10
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?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
soupybionics
  • 4,200
  • 6
  • 31
  • 43

1 Answers1

6

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);
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Calculator
  • 2,769
  • 1
  • 13
  • 18
  • What triggers `m` to be copied in __block1__ ? I mean clearly in this case `fn main() { let mut m:i32 = 10; let n = { let b = &mut m; *b = 12; &*b }; println!("{:?}", n); }` , it is not. Is the copied triggered because of outer reference? – soupybionics Jul 30 '18 at 13:21
  • A temporary mutable reference will always take the ownership (copy/move) of the value it is pointing to? – soupybionics Jul 30 '18 at 13:30
  • 1
    I can see the parallel between `&mut` and `S`, but this can't be the whole story. For one thing, [block1 will work if you remove the extra layer of `&`](http://play.rust-lang.org/?gist=80efb0bfa11cccf7add174435a74c4c5&version=stable&mode=debug&edition=2015), whereas the version with `S` will still fail to compile – trent Jul 30 '18 at 14:20
  • block1 with only 1 `&` (i.e `&mut`) is working because there is no copy (no ownership) involved and there is no temporary (akin to rvalue?) here, I guess. So that's why I asked is the ownership taken because it is a temporary ref. (incase of `&&mut` i mean) – soupybionics Jul 30 '18 at 14:43
  • @soupybionics Obviously, `S` is too simplistic to model the full semantics of `&mut`. My guess is that the outer reference forces the compiler to base its reasoning on some stricter model for `&mut` which is closer to `S` and therefore cannot guarantee that the code is safe, while in the case where the outer reference is missing the compiler can make advanced use of its knowledge about `&mut` to infer that the code is indeed safe. – Calculator Jul 30 '18 at 14:54
  • @Calculator The `temporary value does not live long enough` is referring to `&mut m` and not just `m` (My guess based on the error message having `^^^^^^` underlining the whole of `&mut m` and not just `m`). So it is the `&mut` which is the _temporary value_ pointing to `m` without taking the ownership (no move/copy). If `&mut` does take the ownership, shouldn't [this code](https://play.rust-lang.org/?gist=4c46ed68f0695c9e6f3ae8266a7ea385&version=stable&mode=debug&edition=2015) fail to compile then ? It passes. – soupybionics Jul 30 '18 at 20:06