Out Of Bound compile error with .len() function call

This is my first time learning Rust language.
I'm studying from the site : Arrays and Slices - Rust By Example

Anyways, I found weird situation.

hello_world % cat src/main.rs 

fn main() {
    let xs: [i32; 5] = [1, 2, 3, 4, 5];
    println!("number of elements in array: {}", xs.len());
    println!("{}", xs[7]);
}
hello_world % cargo build
   Compiling hello_world v0.1.0 (/Users/cheil/work/dev/rust/hello_world)
    Finished dev [unoptimized + debuginfo] target(s) in 0.78s

The above DOES NOT produce a compile error of 'index out of bound'.
If I comment out the line where xs.len() is called...

hello_world % cat src/main.rs

fn main() {
    let xs: [i32; 5] = [1, 2, 3, 4, 5];
    // println!("number of elements in array: {}", xs.len());
    println!("{}", xs[7]);
}
hello_world % cargo build    
   Compiling hello_world v0.1.0 (/Users/cheil/work/dev/rust/hello_world)
error: this operation will panic at runtime
 --> src/main.rs:5:20
  |
5 |     println!("{}", xs[7]);
  |                    ^^^^^ index out of bounds: the length is 5 but the index is 7
  |
  = note: `#[deny(unconditional_panic)]` on by default

error: could not compile `hello_world` due to previous error

compiler error occurs.

So how does that .len() function call affect this?

4 Likes

Note this is only a lint. It is not a hard error.

This particular compiler error is actually just a "strong warning". You can disable it by adding #![allow(unconditional_panic)] at the top of your file.

As for what len has to do with it, well, the compiler really wants to make sure that this error doesn't happen in cases where it shouldn't. I'm guessing that the code generated by the println! macro has confused the warning into being unsure whether you always reach the xs[7] line.

4 Likes

This code doesn't trigger the lint too:

fn main() {
    let xs: [i32; 5] = [1, 2, 3, 4, 5];
    let _ = xs.len();
    println!("{}", xs[7]);
}

I guess that compiler thinks that xs.len() can panic itself.

Ah, good point, that makes sense. If the len call panics (or loops forever), then you don't reach the xs[7] line, and it only prints if you always reach the line.

1 Like

Um... how .len() call can panic????

It can't, but the warning doesn't know.

4 Likes

Curiously, this code does trigger the lint:

fn main() {
    let xs: [i32; 5] = [1, 2, 3, 4, 5];
    if false {
        println!("{}", xs[7]);
    }
}

...although the problematic code is not only potentially unreachable, but actually unreachable.

8 Likes
fn main() {
    let xs: [i32; 5] = [1, 2, 3, 4, 5];
    let _ = xs[0];
    println!("{}", xs[7]);
}
hello_world % cargo build
   Compiling hello_world v0.1.0 (/Users/cheil/work/dev/rust/hello_world)
error: this operation will panic at runtime
 --> src/main.rs:6:20
  |
6 |     println!("{}", xs[7]);
  |                    ^^^^^ index out of bounds: the length is 5 but the index is 7
  |
  = note: `#[deny(unconditional_panic)]` on by default

error: could not compile `hello_world` due to previous error

I cannot find a lot of samples, but it will NOT pass lint except uses of len call.

The thing to understand about warnings is that its very important that if the warning triggers, it should be because there actually is an issue.

On the other hand, if a warning doesn't trigger when it "should", then that is not a big deal.

1 Like

Actually, I have a different opinion.
If a warning doesn't trigger when it should, that means I will miss out some important error that the compiler 'should have seen'.
So, I think this is a big deal when many people told me that the Rust compiler is 'smart and reliable'.

Rust has some guarantees that are 100% reliable, and some that are not.

This particular guarantee can never be 100% reliable. There are mathematical proofs that guarantee this. Would it be better to entirely remove the warning so as to only have the guarantees that help every time left?

4 Likes

If a warning triggers when it shouldn't, over time you're more likely to dismiss it as "false positive" when it's indeed relevant. This is a tradeoff, yes. Rust for its own lints chooses to go for "no false positive, possible false negative". Clippy partially goes for another way - "this lint can have many false positives, but it's allow-by-default; if you want to sort them manually - deny it explicitly".

5 Likes

This, I completely agree.
But "doesn't trigger when it should", that I do not.

Anyways, it seems like I'm disparaging Rust, and absolutely this is not my intention.
I just want to get some idea why len makes different in the compiler. :sweat_smile:

It is not even the len(), it is the cast to slice (Rust Playground):

fn main() {
    let xs: [i32; 5] = [1, 2, 3, 4, 5];
    _ = &xs as &[i32];
    _ = xs[7];
}

Now that is really weird.

Edit: No, wait, it is not even the cast, it is the fact the a reference was taken.

3 Likes

To be clear, there isn't really anything to learn about the language from this warning. This particular warning has some weird heuristics to avoid false positives, but those heuristics result in false negatives instead.

For errors that are actually properly part of the language, the rules are more consistent.

I'm not sure I understand what you mean by this. Do you agree that we should avoid false positives or not?

1 Like

In this case, indexing out of bound and triggering warning that it will cause panic in runtime is NOT false positive. It is in fact, true positive.
The array has length of 5, and I'm referencing index 7.
This will crash.

Problem is, either one or another is in some cases unavoidable (i.e. there's always code which is correct and the one which is incorrect, neither of which can be proven so). This specific case might be solvable (I'm not sure how it is implemented, really), but there's always something where we must choose the risk of either false positive or false negative.

2 Likes

Yes, I understand how to 'know' is the difficult to implement.
So, again, I'm not trying to debate the compiler and lint is defective or perfect.
I'm just trying to understand why len is making the difference.

There's not really anything to learn about len or Rust here. This particular warning has some weird heuristics for when it should trigger. Apparently creating a reference to it breaks it for some reason. (calling len involves creating a reference to it)

Real errors that are an actual part of the language have more consistent rules.

5 Likes