What the code should do
The task is to add one to every even number in nums and subtract one from every odd number. For nums = [1, 2, 3, 4, 5] the vector should end up as 0, 3, 2, 5, 4, and the final loop prints those five numbers, one per line.
This is the code you start with:
let mut nums = vec![1, 2, 3, 4, 5];
for &num in &nums {
if num % 2 == 0 {
num += 1;
} else {
num -= 1;
}
}
for n in &nums {
println!("{}", n);
}
It doesn't compile. Even after the compile error is fixed, a second problem would remain: the vector would not change.
What goes wrong
First, the compiler error. The pattern &num in for &num in &nums takes each element out of the borrowed vector and copies it into a new variable named num. Variables in Rust are immutable unless declared with mut, and this one isn't. Both num += 1 and num -= 1 are therefore rejected:
error[E0384]: cannot assign twice to immutable variable `num`
Second, the copy. Suppose you silenced the error by making the binding mutable, for example with for mut num in nums.iter().copied(). The program would compile, and it would print 1 2 3 4 5 unchanged. A loop variable is a copy of the current element, not the element itself. Changing the copy leaves the vector alone, in the same way that changing number_copy after let mut number_copy = number; leaves number alone.
So the real fix isn't "add mut". The loop has to reach the elements inside nums.
The fix: loop over the indices
An index identifies a place in the vector. Writing nums[i] += 1 changes the element stored at that place, not a copy. So loop over the indices 0..nums.len() and read and write through nums[i]:
- Replace the loop header with
for i in 0..nums.len(). The vector stays declared withlet mut, which is what lets you change its elements. - Test the element with
nums[i] % 2 == 0. This reads the element at positioni. - Change the element with
nums[i] += 1for even numbers andnums[i] -= 1for odd ones. - Leave the printing loop alone. It only reads, so it can keep borrowing
&nums.
The fixed code in Rust
let mut nums = vec![1, 2, 3, 4, 5];
for i in 0..nums.len() {
if nums[i] % 2 == 0 {
nums[i] += 1;
} else {
nums[i] -= 1;
}
}
for n in &nums {
println!("{}", n);
}
The output:
0
3
2
5
4
Rust details worth knowing:
- Indexing assigns in place.
nums[i] += 1works becausenumsis declaredmut. Withoutmutonnums, this line would fail to compile. - Reading and writing in one iteration. The index loop holds no borrow of the vector between iterations, only the integer
i. That is why readingnums[i]and then writing it is allowed. - The sign of
%. Rust's remainder takes the sign of the left operand, so-3 % 2is-1. Testing for== 0classifies negative numbers correctly:-2becomes-1and-3becomes-4. - Another valid fix.
for num in &mut numsgivesnumas an&mut i32, a reference to the element itself, and you write*num += 1. That also changes the vector, and it needs the*to reach the number. The lesson uses indices because they work for any task where the position matters.
Common mistakes
- Adding
mutto the loop variable only. The error disappears but the vector doesn't change, so the output is still1 2 3 4 5. - Changing the vector while looping over
&nums. Writingnums[0] += 1insidefor n in &numsis rejected: "cannot borrownumsas mutable because it is also borrowed as immutable". The loop holds a shared borrow for its whole duration. - Mixing up the two cases. Even numbers go up and odd numbers go down. Reversing them gives
2 1 4 3 6. - Dropping
mutfromnums. The task keeps the declaration as it is, and for good reason: the elements can't be changed without it. - Forgetting the last element. The range
0..nums.len()already covers every index.0..nums.len() - 1would skip the last element.
Key takeaways
- A loop variable that holds a value is a copy; changing it never changes the vector.
- To change elements, go through the vector: either by index,
nums[i], or by a mutable reference from&mut nums. - Variables and vectors are immutable by default in Rust. Add
mutonly where something really changes.
