为什么Rust在运行时检查数组边界,而(大多数)其他检查发生在编译时?

k7fdbhmy  于 2022-11-24  发布在  其他
关注(0)|答案(2)|浏览(184)

阅读basic introduction
如果尝试使用数组中没有的下标,则会出现错误:在运行时对数组访问进行边界检查。
为什么Rust在运行时检查数组边界,而大多数其他检查似乎都发生在编译时?

mwecs4sa

mwecs4sa1#

因为在一般情况下,在编译时检查索引是不可行的。即使对于小程序来说,推理任意变量的可能值也是困难和不可能的。没有人希望必须:
1.形式上证明索引不会超出界限,并且
1.将证明编码到类型系统中
...对于 * 每一个 * slice/Vec/etc.访问,因为这是你在编译时执行边界检查所必须做的。你本质上需要依赖类型。
除了可能使类型检查变得不可判定(并且使程序进行类型检查变得非常困难)之外,类型推断通常变得不可能(并且在最好的情况下受到更大的限制),类型变得更加复杂和罗嗦,语言的复杂性显著增加。只有在非常简单的情况下,不需要程序员额外的大量工作,才能证明索引在界限内。
此外,没有什么动机去摆脱边界检查。生命周期通过几乎完全消除垃圾收集的需要来发挥作用--垃圾收集是一个巨大的、侵入性的特性,具有不可预测的吞吐量、空间并且可以在性能关键的部分中选择性地关闭它,即使整个程序的其余部分都自由地使用它。
请注意,编译器 * 可以 * 对 * 数组 * 的越界访问进行一些简单的检查:
第一个
然而,这是有限制的,并且通过使索引值不是“明显的”常数可以容易地避免:

let a = [1, 2];
let idx = 100;
let element = a[idx];
6gpjuf90

6gpjuf902#

其原因是因为:虽然 * 数组的长度 * 可能提前知道,但被引用的 * 索引 * 可能提前不知道。
简单示例:

fn main() {
    let a = [1,2,3,4,5];
    let index = 10;

    let element = a[index];
}

在rust中,数组是堆栈变量,所以它们的长度不会改变。因此数组的长度在编译时就知道了。
看过上面的例子后,rust似乎应该能够在编译时检查“index out-of-bounds”错误。
然而,虽然数组大小在编译时是已知的,但数组被引用的索引在编译时通常是未知的。
请考虑以下更复杂的示例:

fn main() {
    let a = [1,2,3,4,5];

    let mut guess = String::new();
    io::stdin().read_line(&mut guess).expect("Failed to read line");
    let guess: u32 = guess.trim().parse().expect("Please type a number!");

    let element = a[guess];
}

访问数组的索引来自用户输入。编译器无法知道访问数组的索引。唯一知道索引的方法是运行程序。这就是为什么rust在运行时处理数组索引越界错误,而不是在编译时。

相关问题