【问题标题】:Why does Rust check array bounds at runtime, when (most) other checks occur at compile time?为什么 Rust 在运行时检查数组边界,而(大多数)其他检查发生在编译时?
【发布时间】:2015-04-07 23:43:20
【问题描述】:

阅读basic introduction

如果你尝试使用不在数组中的下标,你会得到一个错误:数组访问是在运行时进行边界检查的。

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

【问题讨论】:

标签: arrays compiler-errors runtime-error rust language-design


【解决方案1】:

因为在一般情况下在编译时检查索引是不可行的。即使对于小程序,推理任意变量的可能值也是困难和不可能的。没有人愿意:

  1. 正式证明索引不能越界,并且
  2. 将该证明编码到类型系统中

...每个切片/Vec/等。访问,因为这是在编译时执行边界检查所必须做的。您基本上需要依赖类型。

除了可能使类型检查无法确定(并使程序的类型检查变得更加困难)之外,类型推断通常变得不可能(并且在最好的情况下受到更多限制),类型变得更加复杂和冗长,并且复杂性语言显着增加。只有在非常简单的情况下,无需大量额外的程序员工作就可以证明索引在界限内。

此外,摆脱边界检查的动机很小。生命周期通过几乎完全消除垃圾收集的需要来减轻负担——这是一个巨大的侵入性功能,具有不可预测的吞吐量、空间和延迟影响。另一方面,运行时边界检查非常无创,开销很小且众所周知,并且即使整个程序的其余部分都大量使用它,也可以在性能关键部分选择性地关闭它。

请注意,编译器可以数组的越界访问做一些简单的检查:

let a = [1, 2];
let element = a[100];
error: index out of bounds: the len is 2 but the index is 100
 --> src/main.rs:3:19
  |
3 |     let element = a[100];
  |                   ^^^^^^
  |
  = note: #[deny(const_err)] on by default

但是,这是有限的,并且可以通过使索引值不是“显而易见的”常量来轻松避免:

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

【讨论】:

  • 建议运行时边界检查是“非常非侵入性的”。影响将与在阵列上工作的算法的复杂性有关。对于像运行时间这样的度量,本质上对每个数组访问添加一个边界检查是一个常数乘数。
  • @Rob 一些操作运行时间的常数因素永远不会改变算法的复杂性。它很可能对(非渐近)运行时间产生不可接受的影响。但是正如我所说的,对于任何单独的数组访问,程序员都可以选择不进行边界检查,如果他们这样做,性能等同于 C。边界检查不会影响程序的任何部分不要使用它。而 that 就是我所说的非侵入性(我在同一句话的其他地方提到了性能影响。)
  • 我没有暗示算法复杂度会有任何变化。能够选择退出运行时功能并不意味着它是非侵入性的。
  • 反过来回答您的问题,侵入性运行时功能是一种具有广泛影响的功能,除非明确禁用(或未使用),否则在现实场景中的运行时可能具有重要意义。也就是说,不可否认的是,对英语医学定义的不完善适应(侵入性手术广泛传播和/或具有强烈的局部影响)。非侵入性功能则相反。顺便说一句:我并不是说运行时边界检查是好是坏,只是说非侵入性这个词似乎有太多的承诺。
  • @ChaseMay Rust 知道数组的长度,但在编译时可能并不总是知道索引值,它可以是一个变量,其值是从函数调用返回的。元组不同的是,元组的索引实际上是字段名(就像结构的字段名),不能是变量。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-01-11
  • 1970-01-01
  • 2020-01-07
  • 2021-11-05
  • 2017-08-25
  • 2019-07-04
  • 1970-01-01
相关资源
最近更新 更多