Tomasz
Tomasz

Reputation: 366

Accessing two consecutive elements of an array in a for loop results in borrow checker error

I am trying to iterate over an array, mutably borrowing its elements one by one. In each iteration, I want to use the results from the previous one. I wrote the code like the one below:

fn main() {
    let mut arr: [Vec<u32>; 3] = [vec![1, 2, 3], Vec::new(), Vec::new()];
    for i in 1..3usize {
        let prev_vec: &Vec<u32> = &arr[i - 1];
        for prev_num in prev_vec {
            arr[i].push(prev_num * 2);
        }
    }
    dbg!(arr);
}

However, it seems that in the line let prev_vec: &Vec<u32> = &arr[i - 1]; instead of borrowing only the vector, the whole array gets marked as borrowed. The compiler errors is

 --> example.rs:6:13
  |
4 |         let prev_vec: &Vec<u32> = &arr[i - 1];
  |                                   ----------- immutable borrow occurs here
5 |         for prev_num in prev_vec {
  |                         -------- immutable borrow later used here
6 |             arr[i].push(prev_num * 2);
  |             ^^^^^^^^^^^^^^^^^^^^^^^^^ mutable borrow occurs here

Why does the borrow checker assume the whole array is borrowed? How can I rewrite this code, so it is accepted by the compiler?

Upvotes: 2

Views: 545

Answers (1)

harmic
harmic

Reputation: 30577

Indexing operations borrow the whole container - you can see that from the definition of the Index and IndexMut traits:

fn index(&self, index: Idx) -> &Self::Output;

The reason for this is that otherwise you could do something to the array that would invalidate the reference to the vec element. For example:

let e1 = arr[1];
arr.push(vec![9,8,7]);

Pushing something onto arr could result in it being re-allocated to a different area of memory, to accomodate the new element. That would mean e1 would be left pointing to nothing.

You can work around that using the split_at_mut method:

fn main() {
    let mut arr: [Vec<u32>; 3] = [vec![1, 2, 3], Vec::new(), Vec::new()];
    for i in 1..3usize {
        let (prev,curr) = arr.split_at_mut(i);
        let prev_arr = prev.last().unwrap();
        let this_arr = curr.first_mut().unwrap();
        for prev_num in prev_arr {
            this_arr.push(prev_num * 2);
        }
    }
    dbg!(arr);
}

Playground

split_at_mut takes a slice and gives you back two mutable sub-slices.

Upvotes: 2

Related Questions