阅读basic introduction:如果尝试使用数组中没有的下标,则会出现错误:在运行时对数组访问进行边界检查。为什么Rust在运行时检查数组边界,而大多数其他检查似乎都发生在编译时?
mwecs4sa1#
因为在一般情况下,在编译时检查索引是不可行的。即使对于小程序来说,推理任意变量的可能值也是困难和不可能的。没有人希望必须:1.形式上证明索引不会超出界限,并且1.将证明编码到类型系统中...对于 * 每一个 * slice/Vec/etc.访问,因为这是你在编译时执行边界检查所必须做的。你本质上需要依赖类型。除了可能使类型检查变得不可判定(并且使程序进行类型检查变得非常困难)之外,类型推断通常变得不可能(并且在最好的情况下受到更大的限制),类型变得更加复杂和罗嗦,语言的复杂性显著增加。只有在非常简单的情况下,不需要程序员额外的大量工作,才能证明索引在界限内。此外,没有什么动机去摆脱边界检查。生命周期通过几乎完全消除垃圾收集的需要来发挥作用--垃圾收集是一个巨大的、侵入性的特性,具有不可预测的吞吐量、空间并且可以在性能关键的部分中选择性地关闭它,即使整个程序的其余部分都自由地使用它。请注意,编译器 * 可以 * 对 * 数组 * 的越界访问进行一些简单的检查:第一个然而,这是有限制的,并且通过使索引值不是“明显的”常数可以容易地避免:
Vec
let a = [1, 2]; let idx = 100; let element = a[idx];
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在运行时处理数组索引越界错误,而不是在编译时。
2条答案
按热度按时间mwecs4sa1#
因为在一般情况下,在编译时检查索引是不可行的。即使对于小程序来说,推理任意变量的可能值也是困难和不可能的。没有人希望必须:
1.形式上证明索引不会超出界限,并且
1.将证明编码到类型系统中
...对于 * 每一个 * slice/
Vec
/etc.访问,因为这是你在编译时执行边界检查所必须做的。你本质上需要依赖类型。除了可能使类型检查变得不可判定(并且使程序进行类型检查变得非常困难)之外,类型推断通常变得不可能(并且在最好的情况下受到更大的限制),类型变得更加复杂和罗嗦,语言的复杂性显著增加。只有在非常简单的情况下,不需要程序员额外的大量工作,才能证明索引在界限内。
此外,没有什么动机去摆脱边界检查。生命周期通过几乎完全消除垃圾收集的需要来发挥作用--垃圾收集是一个巨大的、侵入性的特性,具有不可预测的吞吐量、空间并且可以在性能关键的部分中选择性地关闭它,即使整个程序的其余部分都自由地使用它。
请注意,编译器 * 可以 * 对 * 数组 * 的越界访问进行一些简单的检查:
第一个
然而,这是有限制的,并且通过使索引值不是“明显的”常数可以容易地避免:
6gpjuf902#
其原因是因为:虽然 * 数组的长度 * 可能提前知道,但被引用的 * 索引 * 可能提前不知道。
简单示例:
在rust中,数组是堆栈变量,所以它们的长度不会改变。因此数组的长度在编译时就知道了。
看过上面的例子后,rust似乎应该能够在编译时检查“index out-of-bounds”错误。
然而,虽然数组大小在编译时是已知的,但数组被引用的索引在编译时通常是未知的。
请考虑以下更复杂的示例:
访问数组的索引来自用户输入。编译器无法知道访问数组的索引。唯一知道索引的方法是运行程序。这就是为什么rust在运行时处理数组索引越界错误,而不是在编译时。