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

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

I think that might be worth opening an issue to fix the lint (not sure how difficult that is though). I believe that taking a shared reference should have no impact here.

Edit: Done, see my post below.

2 Likes

See also Issue #90534. I added your example as a comment to that issue.

I opened a new issue #98444 with the following example code:

fn main() {
    let xs: [i32; 5] = [1, 2, 3, 4, 5];
    let _ = &xs; // this line suppresses the `unconditional_panic` lint
    let _ = xs[7];
}
2 Likes

This lint is emitted by the const propagation pass I believe. This pass forgets about the value of locals when a reference is taken.

Thank you for taking an action!
Glad I can help.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.