Multiple Mutable Borrows Puzzle

Why does the compiler disallow the first form, while allowing the second form, basically allowing two mutable borrows of a to co-exist at the same time?

One could argue that if we try to use the first mutable borrow of a before the second one, in the second example, it will stop compiling.
But why isn't this applicable to the first example, where it won't compile regardless of the order in which we use the two borrows of a?

Playground Link

fn main() {
     {
        // This doesn't compile
        let mut a = vec![100];
        let b = &mut a;
        {
            let c = &mut a;
            c.push(10);
        }
        b.push(20);
        println!("Final {:?}", a)
    }
    {
        // This compiles
        let mut a = vec![100];
        let b = &mut a;
        {
            let c = &mut *b;
            c.push(1);
        }
        b.push(20);
        println!("Final {:?}", a)
    }
}

In the second example, c is a reborrow of *b. While the reborrow is active, b is inactive; once the reborrow expires, b can be active again. Even though the two &mut exist, only one is active at a time, so they still provide exclusivity.

Thus "no two &mut" or "no &mut and &" is an oversimplification of Rust's aliasing requirements. It's closer to "if a &mut is active, no other observer can be active".

Reborrows are actually happening all the time, which is why this works even though &mut is not Copy.

fn foo(v: &mut Vec<i32>) {
    v.push(0);
    println!("{v:?}");
}

In the first example, there is no reborrowing. You're using something (a) that gets used in a way (borrowing it exclusively to create c) that cancels any active borrows.

You can perhaps think of the allowed reborrows as some tree of delegation. If b is to remain usable after the creation of c, b has to be involved in some chain of reborrows that leads from c back to b. Instead in the first example, c borrowed a, which is above b in the tree.

4 Likes

The problem is that it is not possible to have two mutable borrows of a at the same time (what the first part of your code is trying to do).

The second part of the code is different, it is reborrowing b.

so this does not compile:

let mut a = vec![100];
let b = &mut a;
{
    let c = &mut *b;
    b.push(20);
    c.push(1);
}
1 Like

The use of b invalidates c in this example, analogous to how the use of a invalidated b in the OP.

Right! I never thought about how &muts get passed around without getting moved, even though they aren't Copy

What exactly is happening in this particular snippet?

let mut v = vec![100];
let x = &mut v;
foo(x);
foo(x);

Does this desugar into?

let mut v = vec![100];
let x = &mut v;
{ let y = &mut *x; foo(y); }
{ let y = &mut *x; foo(y); }

Yes, if foo takes a &mut _, that's pretty much what happens (or just foo(&mut *x)). Each reborrow lasts only as long as the call to foo.

(Most transformations or things like operator resolution happen at some non-syntactical level, so I always take it with a grain of salt when I read about a Rust "desugaring" or "equivalent to" as I've encountered a number of exceptions, but in this case I can't think of any difference.)

2 Likes

@quinedot I have found out that { let y = &mut *x; foo(y) } and foo(&mut *x) behaves differently.

I've turned an example for that into a new question:

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.