We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Ownership II: Borrowing and the Borrow Checker step 14 of 24
iter_mut(): mutating every element without ever holding two &mut
Two in-place functions, applied in that order by the harness.
pub fn running_max_in_place(xs: &mut [i64])
pub fn zip_add(dst: &mut [i64], src: &[i64])
running_max_in_place replaces each element with the maximum of itself and
everything before it. [3,2,1] becomes [3,3,3]; [1,2,3] is unchanged.
zip_add adds src[i] to dst[i] for as far as both slices go. If they
differ in length, the extra elements of the longer one are simply not used —
in either direction. dst=[5,5], src=[1] gives [6,5]; dst=[1], src=[2,3] gives [3]. Both directions are tested.
The specification says in place. A scan-based one-liner that builds a new
vector is elegant and is not what is being asked for; there are cases pinning
the in-place behaviour.
The starter does not compile.
The thing that looks like it should be illegal
for x in xs.iter_mut() {
*x *= 2;
}
Stop and consider how strange this is under the rule you learned in item 3.3.
xs.iter_mut() takes &mut xs — an exclusive loan of the whole slice — and
hands you an iterator that holds it. Then next() gives you a &mut i64
pointing into that slice. So now there are two exclusive references to
overlapping memory: the iterator’s, and yours. That is the exact thing E0499
exists to forbid. Why is this allowed?
Beginners either never notice, or notice and conclude that iter_mut is magic
they should not imitate. Both are bad outcomes, because the pattern is the
answer to about a third of all beginner borrow errors.
Why it is sound
The answer is in Iterator‘s definition:
trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}
Look at what is missing. Item is an associated type with no lifetime
parameter, and no connection to the &mut self in next. For
IterMut<'a, T>, Item = &'a mut T — tied to 'a, the lifetime of the
original slice, not to the borrow of the iterator.
That is the whole trick. The reference next() returns outlives the borrow of
the iterator that produced it. The iterator is free again the instant next()
returns, so there is only ever one live exclusive reference to any given
element at any time, and elements never overlap because the iterator walks
forward and never revisits.
The Nomicon says it directly: this is why iter_mut can be written safely at
all, and it is also why the “lending iterator” (an iterator whose items borrow
from the iterator itself, so only one can exist at a time) is a genuinely
different and much harder thing that Rust only gained the machinery for
recently.
The practical takeaway: iter_mut is not an exception to the rule. It is a
demonstration that the rule is about overlapping live references, and a
forward-only walk never produces two.
Carrying state across the loop
running_max_in_place needs a value that survives from one element to the
next. That is fine — a plain local:
let mut best = i64::MIN;
for x in xs.iter_mut() {
best = best.max(*x);
*x = best;
}
best is an i64, not a reference, so it has nothing to do with the borrow
of the slice. Read through *x, write through *x, keep the accumulator
outside. This shape covers running sums, running maxima, prefix products,
deltas, and most of what you would otherwise reach for scan to do — with no
allocation.
i64::MIN as the seed is correct here: any real element is >= it, and the
empty slice never enters the loop.
Why the starter fails
let prev = &mut xs[i - 1];
let cur = &mut xs[i]; // error[E0499]
Two exclusive loans of the same slice, live at the same time — item 3.7’s
error, in the shape people most often reach for when they need “this element
and the one before it”. iter_mut plus an accumulator dissolves it, because
you never need prev as a reference: you only need its value, and a local
holds that perfectly.
When you genuinely need two references into one slice at once —
neighbours, halves, arbitrary index pairs — that is items 3.7 and 3.16:
get_disjoint_mut, split_at_mut, chunks_mut, windows. Not this one.
zip truncates
for (d, s) in dst.iter_mut().zip(src) {
*d += *s;
}
Iterator::zip stops when either side stops. That is exactly the
“whichever is shorter” rule the spec asks for, in one call, with no length
comparison and no bounds check. Write it with indices and you get either a
panic or a needless_range_loop from clippy, and probably both.
(manual_slice_fill is a related lint: a loop writing the same constant into
every element should be slice::fill.)
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.