Why index out of bound is not checked at Compile Time

when i access an element from an array. like below… Rust compiler why couldn’t show compiler time error, rather than run time panic.

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

let element = a[index];

println!("The value of element is: {}", element);

Because indexing is desugared to Index::index or IndexMut::index_mut very early on the compilation process, and Rust does not check if arbitrary functions are guaranteed to panic because it can’t know if it can even compute the function at compile time. Heck, it doesn’t even even warn that this print statement is unreachable.

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=62fb31a5c3d52b7d88a6513feaeba75a

fn main() {
    if true { panic!(); }
    
    println!("hi");
}

While Rust could possibly check this, it would either need to be special cased (many in the Rust team would be against this) or you would need to make the compiler significantly more powerful to handle random expressions at compile time.

You means some team members are also willing to provide this simple feature to detect out of bound index at compile time… ???

I am just speculating based on the fact that in the past team menbers were against special casing other parts of the language. I don’t know if there are Rust team members who want this. This may be fine as a Clippy lint.

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

This only works with constants though.

1 Like

so clippy has developed such kind of lint to detect such kind of error. ???

is this developed by clippy lint ? means which tool could detect this error…

No, this is not clippy, but the rust internal linter.
Clippy hasn’t such linter for arbitrary indexing.

Doing it properly at compile time requires deeper analysis of the code, which would slow down compilation time (which is already too slow for Rust).

The benefit of it is very small, because fixed-size arrays are rarely used in Rust, constant-but-indirect indexing is also not that frequent, and an erroneous combination of the two is even rarer.

It’s still checked at run time, so there’s no security issue, just an inconvenience.

So it’d slow everyone down for a small convenience for a few cases.

5 Likes

Well, from another perspective, it would slow down the compile to improve runtime speed, which makes it sounds better. If you do bounds checking at compile time then you don’t have to do it at runtime, which would be awesome.

1 Like

No, that’s different. LLVM will eliminate runtime bounds checks when it can prove they’re redundant (including this particular case if the index was in range).

The code seen by the compiler is different from the code seen by the optimization passes, and it’s not easy to feed this information back from the optimizer.

4 Likes

So you’re saying the optimizer can eliminate bounds checks sometimes by proving them redundant, but can’t prove that the check will fail and trigger a compile time error. That makes sense actually. It would be lovely if there were a way to get this information back from the optimizer so we could see compile time errors more often.

1 Like

Note that indexing (along with my fork, indexing-str) provide truly sound compile-time checked indices and ranges (in exchange for some bogus lifetime errors when you get it wrong).

Fixed size arrays definitely aren’t as useful as they could be in Rust. It comes down to const evaluation, though: as more things become const-evaluable and Rust does more Mir-level inlining, you’ll see more “this array access will always be out of bounds” caught.

5 Likes

I agree and understand. But runtime crashed and panic will be more worst than compile time… if am not on wrong direction… !

What you want is dependent typing. It is in short a technique to typecheck over values like numbers and range. The reason why Rust doesn’t support such feature is basically nobody implemented it yet.

Beside, there’s a feature called “const generics” which enables polymorphism over constants, like array length. Core teams did really hard work on it and it will (hopefully) be merged to nightly soon after initial impl.

Sure, but it’s hard. The compiler could add some extra reasoning to handle this case, but then what about Vec? It’s probably a more commonly used type.

let v = vec![1,2,3];
v[10];

but Vec is technically quite different from arrays, so even this simplest case isn’t caught. And what about:

let mut v = vec![];
v.push(1);
v.clear();
v[0];

Now the compiler has to know about all of Vec’s methods! And what about:

let mut v = vec![1,2,3];
let cond = true;
if cond {v.clear();}
v[0];

That’s another case that could be statically known, but requires the compiler to symbolically execute code, including conditional code (with nested ifs that gets quadratic!), and track state of simulated values. There are static analysis tools that do these things, but it’s complicated and expensive. And in the end all it finds is code that is always broken, so you’ll always find that bug as soon as you run this code anyway.

7 Likes
  1. Because of Halting problem, the compiler can not detect all out-of-bounds at compile time.

  2. Thus, humans can always construct a program where (1) it is “obvious” that there will be an out-of-bound error and (2) the Rust compiler can not detect it at compile time.

  3. Now, it is possible by adding layers and layers of hacks, to have the compiler detect all these toy examples – or we can just keep the compiler simple and not deal with this.

thanks so much for detailed response… that is the perfect answer, i think to convince someone like me… thanks again

Just for completeness sake, I think this thread should mention that you could forgo the convenience of direct indexing and instead use get which returns an Option.

2 Likes

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.