We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Ownership II: Borrowing and the Borrow Checker step 13 of 24
Indexing in a loop is usually a borrow-checker workaround — and clippy knows
pub fn subtract_min(xs: &mut [i64]) -> i64
Find the smallest element, subtract it from every element in place, and return
the value that was subtracted. An empty slice is untouched and returns 0.
[] -> 0, []
[5] -> 5, [0]
[3,3,3] -> 3, [0,0,0]
[-2,4,0] -> -2, [0,6,2]
[0,7] -> 0, [0,7]
The starter compiles and passes every test. It fails the grade. Read the clippy output before reading any further here.
Two lessons that turn out to be the same lesson
This item sits at the junction of “satisfy the borrow checker” and “write good Rust”, and the point is that they converge. The iterator rewrite is simultaneously safer, usually faster, and makes borrow errors disappear. That is not a coincidence, and it is worth understanding why.
The borrow lesson, hiding inside
The natural draft is:
let m = xs.iter().min(); // Option<&i64> — a SHARED loan of xs
for i in 0..xs.len() {
xs[i] -= m.unwrap(); // needs an EXCLUSIVE loan -> E0502
}
The shared loan created by .iter().min() is still alive inside the loop,
because m is a reference into the slice and you keep using it. So the
mutation is rejected.
What actually fixes it is not the loop at all: it is getting the i64 out
of the Option<&i64>. Whether you write *v, .copied(), .cloned(), or
destructure with Some(&m), the effect is the same — you end up holding an
independent integer, the loan dies on that line, and the mutable loop is free
to start.
That is worth saying explicitly because it is easy to miss: the
dereference is the fix, not the loop rewrite. The starter already does it
(Some(v) => *v), which is exactly why the starter compiles.
The clippy lesson, sitting on top
error: the loop variable `i` is only used to index `xs`
--> src/lib.rs:6:14
|
6 | for i in 0..xs.len() {
| ^^^^^^^^^^^
= note: `-D clippy::needless-range-loop` implied by `-D warnings`
help: consider using an iterator
|
6 | for <item> in xs.iter_mut() {
needless_range_loop is the gate here. Its argument is not aesthetic:
-
xs[i]is a bounds-checked access. Every iteration pays a compare and a branch. LLVM often eliminates it for a simple0..lenloop, but “often” is doing real work in that sentence — put anything non-obvious in the loop and the check stays. -
iter_mut()cannot be out of bounds by construction, so there is nothing to check and nothing to eliminate. - The iterator version cannot have an off-by-one. It is not that off-by-ones are unlikely; it is that the expression is not there to be wrong.
The idiomatic form is for x in xs.iter_mut() { *x -= m; }.
Related lints in the same family: manual_memcpy (a loop copying src[i]
into dst[i] — use copy_from_slice), explicit_counter_loop (a hand-rolled
i += 1 alongside a for — use .enumerate()), and mut_range_bound (you
changed n inside for i in 0..n, which does nothing because the range was
built before the loop began). indexing_slicing is a stricter,
allow-by-default restriction lint that bans xs[i] outright in codebases that
want no panicking paths at all; it is not part of this gate.
Being honest: when indexing is right
Clippy is a linter, not an oracle, and this particular lint has a history of
suggesting code that does not compile (rust-clippy issues #16344, #5625,
#2036, #15596 among others). #[allow(clippy::needless_range_loop)] with a
one-line comment explaining why is a legitimate professional outcome, not
a defeat. This gate will not accept it, which is why this problem was chosen
to be one where the iterator form genuinely wins.
The honest list of cases where the index is the right tool:
-
Two independent indices.
while i < n && j < mmerging two sequences. Two iterators would needpeekableand be less clear. -
Non-unit stride.
for i in (0..n).step_by(3)is fine, but if the pattern is irregular, indices win. (chunks/windowscover the regular cases.) -
Wraparound.
v[(i + 1) % n]— a neighbour relation that an iterator does not express.windows(2)handles the non-wrapping version. -
The index is the subject. Binary search, dynamic-programming tables,
graph adjacency by node id. Here
iis not a way to reach the data; it is the data.
Rule of thumb, and the exact thing clippy’s heuristic is approximating: if the
loop variable appears only inside [...], it is a workaround and the
iterator is better. If the loop variable appears anywhere else — in
arithmetic, in a comparison, in the output — it is real and indexing is
honest.
Remember the grade is compile + tests + clippy -D warnings.
Loading visualization…
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.