Warn mutable range bound variable?


#1

rust considers the following code fine despite that at the beginning it surprised me that the iteration still took 4 times.
I think there should be a lint for it. The pattern is that there is a mutable binding that is used as a range bound (or more precisely, only when the variable may be updated in the range loop). It might be too heavy for rustc itself, but can be a linter of rust-clippy?

fn main() {
  let mut m = 4;
  for i in 0..m {
    if i == 2 {
      m = 2;
    }
    println!("i={}, m={}", i, m);
  }
}

#2

tl;dr this might belong in clippy, but not for the reasons you think it might.

What’s happening is that 0..m expands to std::ops::Range {start: 0, end: m}. In other words, m is copied into a brand-new variable, so you actually have two variables: m (what you’re modifying), and an anonymous Range that you can’t see or touch.

Even if you manually specify a Range and try to modify it:

fn main() {
    let mut range = std::ops::Range {start: 0, end: 4};
    for i in range {
        if i == 2 {
            range.end = 2;
        }
        println!("i={}, m={}", i, range.end);
    }
}

Rust won’t let you because for loops consume the specified Range:

error[E0382]: use of moved value: `range.end`
  --> src/main.rs:16:35
   |
12 |     for i in range {
   |              ----- value moved here
...
16 |         println!("i={}, m={}", i, range.end);
   |                                   ^^^^^^^^^ value used here after move
   |
   = note: move occurs because `range` has type `std::ops::Range<i32>`, which does not implement the `Copy` trait

error: aborting due to previous error(s)

Modifying a loop’s range bounds in the middle of said loop is a massive footgun, and Rust rightly stopped you from pulling the trigger.

As far as adding new lints goes, the clippy maintainers are always accepting suggestions via bug reports, so ahead and give it a shot. :slight_smile:


#3

Thanks, @FaultyRAM ! I’ll open an issue on clippy repo.

For the range syntactic sugar, to be honest, I actually prefer to the explicit one (but may adapting terse syntax like in Scala or Python). Seems I need time to get comfortable with the Rusty way…