Why this code won't compile? it compiles until x using case but problem with z using case

fn main(){
   
   
    let mut z = 4;
    let mut x = &mut z;
    let mut f = &mut x;
  
    let mut q = 44;
    let mut s = &mut q;
    
    println!("{}",f);
    println!("{}",x);
    println!("{}",z);
    
    f= &mut s;
    
    
} 

(Playground)

1 Like

This is a more complicated explanation that I'd prefer, but hopefully it's still useful.

    let mut s = &mut q;

    println!("{}",f);
    println!("{}",x);
    println!("{}",z);

    f = &mut s;

Let's say s: &'s mut i32. The duration of the borrow of q is represented by that lifetime, 's. In order for the assignment to be valid, that borrow has to be active for the rest of the code in the snippet. The borrow is active while all the println! happen.

    let mut z = 4;
    let mut x = &mut z;
    let mut f = &mut x;

Let's say f: &'f mut &'x mut i32. 'x is the duration of the borrow of z, and 'f is the duration of the borrow of x. Variables don't change type,[1] so we must be assigning a &'f mut &'x mut i32 after the println!s. That in turn means that 'x and 's must be the same.

The compiler actually can tell that overwriting f doesn't read through f, but the lifetime relationships mean that 'x is kept active throughout the println!s anyway, because 's is active throughout the println!s, and 'x and 's are the same.

As a result, z remains exclusively borrowed[2] throughout the println!s. But that conflicts with any other use, such as printing. So you get the error:

error[E0502]: cannot borrow `z` as immutable because it is also borrowed as mutable
(The error would be more accurate if it was phrased: "cannot create a shared borrow of `z` because it is also exclusively borrowed".)

And that's why it points to &mut s -- that's an expression whose type contains 's, which is equal to 'x, which is causing the borrow of z to stay active.


I don't know if this corresponds to practical code or just an experiment. If it corresponds to practical code, the best advice I can offer given the reduced example is to create a new variable instead of overwriting f.

     println!("{}",z);

-    f = &mut s;
+    let f = &mut s;

By creating a new variable, we avoid forcing some lifetimes to be the same due to variables not changing their type.


Bonus round. This is a bit more complicated.[3]

Before I said:

The compiler actually can tell that overwriting f doesn't read through f

And what I meant is that here:

    println!("{}",f);
    // (A)
    println!("{}",x);
    // (B)
    println!("{}",z);
  
    f = &mut s;

The compiler sees that a reference isn't being used after the println! until it is is overwrote. In compiler parlance, the value in f is dead at (A). And x is dead at (B) just because it has no further uses at all.

In terms of lifetimes, that means there can potentially be a "hole" in the lifetimes too: they have to be active for the println!s, and they can be active again after the assignment too, but at (B) they don't have to be active due to any use of f or x.

'x ended up being active at (B) for other reasons anyway (the relationship with 's), which I attempted to explain before.

But if that didn't happen: the compiler is smart enough to let a borrow end when it hits a hole in a lifetime. Therefore, another change that allows the program to compile is:

-    let mut s = &mut q;
    
     println!("{}",f);
+    // (A)
     println!("{}",x);
+    // (B)
     println!("{}",z);
    
+    let mut s = &mut q;
     f= &mut s;

Now 's no longer spans the println!s and 'x has a hole starting at (B), so the borrow of z is no longer forced to be active during the println!("{}",z).

This also helps explain why println!("{}",x) didn't error before: nothing forces 'f to be active during the println!s in the OP, so even in the OP it had a hole at (A), and the borrow of x could end in time.


  1. and the T in a &mut T is invariant ↩︎

  2. &mut _s are exclusive borrows ↩︎

  3. It's harder to explain how the compiler was able to prove something correct, than to approximate why it rejected something :slight_smile: ↩︎