Search code examples
rustmutabledereferenceborrowing

When an immutable reference to a mutable reference to a value outside the scope is returned, why is the mutable reference dropped when the scope ends?


fn main() {
    // block1: fails
    {
        let mut m = 10;

        let n = {
            let b = &&mut m;
            &**b // just returning b fails
        };

        println!("{:?}", n);
    }

    // block2: passes
    {
        let mut m = 10;

        let n = {
            let b = &&m;
            &**b // just returning b fails here too
        };

        println!("{:?}", n);
    }
}

block1 fails with the error:

error[E0597]: borrowed value does not live long enough
  --> src/main.rs:7:22
   |
7  |             let b = &&mut m;
   |                      ^^^^^^ temporary value does not live long enough
8  |             &**b // just returning b fails
9  |         };
   |         - temporary value dropped here while still borrowed
...
12 |     }
   |     - temporary value needs to live until here

Am I correct in assuming that the inner immutable reference is extended beyond the block2 scope, whereas in block1, the inner mutable reference is always dropped even if there is an outer reference to it?


Solution

  • It is sufficient here to think of a mutable borrow as a non-Copy struct (S in the snippets below) which takes ownership of the referenced value. This model represents the exclusive nature of a mutable borrow.

    Reasoning based on this model: In block2 n is a reference to the original m, while in block1 n would end up being a reference to a copy of m owned by the mutable borrow. In both blocks the inner reference is dropped at the end of the let-block, but only in block1 this causes a problem, because in block1 the target of the reference for n is still owned by the inner reference when this inner reference is dropped.

    struct S { m: i32 }
    let mut m = 10;
    
    let n = {
        let s = S { m };
        let b = &s;
        &(*b).m
    }; // s is dropped
    
    println!("{:?}", n);
    

    In the snippet above s takes ownership of a copy of m. The reference n would point to that copy of n which is dropped when s is dropped - not allowed. If m was non-Copy, m would be moved into s, which would have the same implications.

    In block2 the original m is borrowed directly without copying it. If you force a copy, you will get the same error as for block1:

    let mut m = 10;
    
    let n = {
        let m2 = m;
        let mut a = &m2;
        let b = &a;
        &**b
    };
    
    println!("{:?}", n);