Start learning free

LESSON 157 OF 414 · LOOPING OVER ARRAY INDICES

Changing Vector Elements in a For Loop in Rust: Fixing the Bug

Lesson Read the full explanation

Suppose you have a vector nums and your task is to increment every number in that vector by one.

The first idea that might come to your mind is to write a for loop like this:

let mut nums = vec![1, 2, 3];

// Increment items:
for mut num in nums.iter().copied() {
  num += 1;
}

// Print the vector after:
for num in &nums {
  print!("{} ", num);
}

but when you run this code, you'll be surprised to see that the output is:

1 2 3

No element in nums changed its value. How is this possible since we clearly do num += 1 for every num in nums?


Let's talk about copies:

Take a look at this code:

let number = 100;

let mut number_copy = number;

number_copy += 1;

println!("{}", number_copy); // Output: 101
println!("{}", number); // Output: 100

Although we wrote let mut number_copy = number;, this doesn't mean that number and number_copy are one and the same.

number_copy is a completely different entitity (stored at a different memory address) than number.

What we did is initialize this new number_copy entity with the value of number. That's why we say the number_copy is a copy of number.

And so number_copy += 1 will only increment number_copy and won't have any effect on number.


The loop variable is just a copy:

This is what also happens with a for loop.

Whenever you write a for loop, the loop variable (in our case num) is just a copy of the current item of the vector, not the item itself.

This code

let mut nums = vec![1, 2, 3];

for mut num in nums.iter().copied() {
  num += 1;
}

works exactly like this code:

let mut nums = vec![1, 2, 3];

let mut num = nums[0];
num += 1;

num = nums[1];
num += 1;

num = nums[2];
num += 1;

Notice that only the variable num is being manipulated, which has nothing to do with the actual entries of the vector nums.

You'll see this better if we also print the value of num on each iteration.

let mut nums = vec![1, 2, 3];

for mut num in nums.iter().copied() {
  num += 1;
  println!("{}", num);
}

// Print the vector after:
for num in &nums {
  print!("{} ", num);
}

The output of this code is:

2
3
4
1 2 3

Looping over the indices:

In order to manipulate the entries of some vector we have to loop over the items' indices:

let mut nums = vec![1, 2, 3];

for index in 0..nums.len() {
  nums[index] += 1;
}

This code works exactly like this one:

let mut nums = vec![1, 2, 3];

let mut index = 0;
nums[index] += 1;

index += 1;
nums[index] += 1;

index += 1;
nums[index] += 1;

In this way we directly manipulate the actual entries of the vector (nums[0], nums[1], etc.)

Assignment:

Fix our code such that it correctly increments (adds one to) the even numbers of nums and decrements (subtracts one from) the odd ones.

STEP 1 OF 1
    Ctrl + Enter to check

    RUST · LOOPING OVER ARRAY INDICES

    Buggy Code Manipulate Array in Rust, explained

    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]:

    1. Replace the loop header with for i in 0..nums.len(). The vector stays declared with let mut, which is what lets you change its elements.
    2. Test the element with nums[i] % 2 == 0. This reads the element at position i.
    3. Change the element with nums[i] += 1 for even numbers and nums[i] -= 1 for odd ones.
    4. 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] += 1 works because nums is declared mut. Without mut on nums, 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 reading nums[i] and then writing it is allowed.
    • The sign of %. Rust's remainder takes the sign of the left operand, so -3 % 2 is -1. Testing for == 0 classifies negative numbers correctly: -2 becomes -1 and -3 becomes -4.
    • Another valid fix. for num in &mut nums gives num as 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 mut to the loop variable only. The error disappears but the vector doesn't change, so the output is still 1 2 3 4 5.
    • Changing the vector while looping over &nums. Writing nums[0] += 1 inside for n in &nums is rejected: "cannot borrow nums as 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 mut from nums. 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() - 1 would 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 mut only where something really changes.
    Keep going with RustAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free