35

Can someone explain why this compiles:

fn main() {
    let a = vec![1, 2, 3];
    println!("{:?}", a[4]);
}

When running it, I got:

thread '' panicked at 'index out of bounds: the len is 3 but the index is 4', ../src/libcollections/vec.rs:1132

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
mhristache
  • 2,038
  • 3
  • 20
  • 19
  • 2
    Probably the same reason that it doesn't throw a compiler error in any other language; the compiler sees `4` as just an expression, which may as well be `f(x)` that may return some other value. – Colonel Thirty Two Jul 22 '14 at 22:04
  • 2
    Yeah but compiler has to see the expression is a constant literal, so that could be checked during compilation. – Dmitry Belyaev Jul 22 '14 at 22:58
  • 1
    It could be added in the future or as a plugin, but it's an optimization that requires some evaluation at compile time to know how long is the vector and a pass to check calls to many methods that retrieve elements. – snf Jul 22 '14 at 23:28
  • 1
    I would guess that the compiler simply does not know what a `Vec` is and that if you start with `Vec::new()` that it's size is 0 and that if you call push on it three times, its size goes to 3 and that the index in the square bracket has to be less than the vector's size. `Vec` is just a library type. The compiler has no special knowledge about it. – sellibitze Jul 24 '14 at 19:42
  • There's a difference between a compile-time error, and a program that you can easily prove will crash at run-time. Your program should correctly panic only at run-time, that is what the lines of code you wrote, tell it to do. Maybe someone can add a lint to the compiler to warn you when do this, but like there's really no purpose and people working on the compiler can better spend their time elsewhere. – Nicholas Pipitone Dec 06 '19 at 00:19
  • I'll point out that this *does* fail to compile if `a` is an array. `let a = [1, 2, 3];` results in [*"this operation will panic at runtime"*](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=11923d41d8565e499f2870fd40ee1222). – kmdreko Mar 18 '21 at 04:39

3 Answers3

30

If you would like to access elements of the Vec with index checking, you can use the Vec as a slice and then use its get method. For example, consider the following code:

fn main() {
    let a = vec![1, 2, 3];
    println!("{:?}", a.get(2));
    println!("{:?}", a.get(4));
}

Rust Playground

This outputs:

Some(3)
None
BinaryButterfly
  • 18,137
  • 13
  • 50
  • 91
mwhittaker
  • 1,745
  • 15
  • 18
29

In order to understand the issue, you have to think about it in terms of what the compiler sees.

Typically, a compiler never reasons about the value of an expression, only about its type. Thus:

  • a is of type Vec<i32>
  • 4 is of an unknown integral type
  • Vec<i32> implements subscripting, so a[4] type checks

Having a compiler reasoning about values is not unknown, and there are various ways to get it.

  • you can allow evaluation of some expression at compile-time: const functions and variables.
  • you can encode value into types: const generic parameters.
  • you can use dependent typing which bridges the gap between types and values

Rust supports the first two since 1.51, and they could be used to get compile-time checked indexing of compile-time indexes in collections of compile-time defined length... But that would require a different interface than the Index trait, something like a.at::<4>() for example.

Thus, with Index, the values are checked at runtime, and the implementation of Vec correctly bails out (here failing).

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • 5
    llvm, rustc's optimizing compiler backend, does however see through these layers of abstraction when it gets down to optimizing the code. So we can see that it does statically compile it down to a failing bounds check in this particular case. – bluss Feb 08 '15 at 12:00
  • Is this answer outdated? It's been more than 6 years since the last edit. – nbro Dec 26 '22 at 11:28
  • @nbro: Not to the best of my knowledge. Some facts just are. – Matthieu M. Dec 29 '22 at 17:53
  • @MatthieuM. Rust has support for "const" keyword in function and creating `const` variables for some time now. It also supports having `struct S();` now. – JiaHao Xu Apr 11 '23 at 04:03
3

Note that the following is a compile time error:

fn main() {
    let a = [1, 2, 3];
    println!("{:?}", a[4]);
}
error: this operation will panic at runtime
 --> src/main.rs:3:22
  |
3 |     println!("{:?}", a[4]);
  |                      ^^^^ index out of bounds: the length is 3 but the index is 4
  |
  = note: `#[deny(unconditional_panic)]` on by default

This works because without the vec!, the type is [i32; 3], which does actually carry length information.

With the vec!, it's now of type Vec<i32>, which no longer carries length information. Its length is only known at runtime.

Finomnis
  • 18,094
  • 1
  • 20
  • 27