soupybionics
soupybionics

Reputation: 4386

Understanding for loop semantics when iterating through a vector containing mutable references

I am trying to understand why the following code fails:

fn main() {
    let mut a = 10;
    let mut b = 20;
    let mut c = 30;
    let p = vec![&mut a, &mut b, &mut c]; // works with [&a, &b, &c]

    for &x in &p { // works with 'x' instead of of '&x'
        println!("{}", x);
    }
}

The error message is:

error[E0507]: cannot move out of borrowed content
 --> src/main.rs:7:9
  |
7 |     for &x in &p {
  |         ^-
  |         ||
  |         |hint: to prevent move, use `ref x` or `ref mut x`
  |         cannot move out of borrowed content

As I understand, the 'borrowed content' is the mutable references to variables a, b, c by the vec!, but what exactly is getting "moved" here? I presume the move is happening in the beginning of the for loop.

I think there are two mutable references (one by vec) being held, but I guess I am not able to deconstruct &x properly for my understanding and I know the answer lies there. I am able to understand why it works if I put ref x in there as suggested by the compiler or use &&mut x, but I'm not understanding the aforementioned case. (i.e &x).

Upvotes: 1

Views: 178

Answers (1)

turbulencetoo
turbulencetoo

Reputation: 3691

This is a little tricky because bindings in Rust can be a little tricky, but first let's see what we're dealing with and start with some code that does compile:

fn main() {
    let mut a = 10;
    let mut b = 20;
    let mut c = 30;
    let p = vec![&mut a, &mut b, &mut c];

    for x in &p {              // note the lack of &x
        println!("{}", x);
    }
}

This prints out the numbers 10, 20, 30 like you'd expect, but why? Lets change the code to get an error that will tell us what x is:

for x in &p {              // note the lack of &x
    x + ();
}

You then see error[E0369]: binary operation + cannot be applied to type &&mut {integer}

What you get out of iterating over &p is a reference to a mutable reference to an integer. Specifically you get a reference to a vector-owned mutable reference to an integer. The loop cannot obtain a copy of that mutable reference because having two outstanding mutable references is a no-no. If you're not moving that mutable reference out of the vector, the for loop will have to settle for having an immutable borrow of that mutable reference. Here's some code that demonstrates what I'm saying:

let borrow = &p[0];
assert_eq!(borrow, &&mut 10);

// Try to get our own `&mut 10` out of `borrow`    
let gimme = *borrow; // error[E0507]: cannot move out of borrowed content

Now let's talk about what doing for &x in &p does. Here are two equivalent loops that give you the same x and also give you the same error.

for &x in &p {           
}

for temp in &p {
    let x = *temp;
} 

This is because for &x in ... is a destructuring binding. You're asserting "&x matches the structure of an item from iterating over &p. I want x to be the part of that match without the first &."

It's similar to this:

let borrow = &p[0];
assert_eq!(borrow, &&mut 10);

// Try to get our own `&mut 10` out of `borrow`    
let gimme = *borrow; // error[E0507]: cannot move out of borrowed content    
let &gimme_gimme = borrow;  // error[E0507]: cannot move out of borrowed content

In this case, &x matches &&mut {integer} where & matches the first & and x then becomes bound to what's left (&mut {integer}).

I've already explained why you cant have your own copy of that &mut {integer}.

Upvotes: 5

Related Questions