Why the code fails to compile

The following code fails, because of drop(c).
While, n has a lifetime longer than c, which extends until the last println.

So, c get dropped before n definitely, but why I can't drop it by hand?

#[cfg(all())]
fn main() {
    let mut a = 1;
    let mut b = &mut a;

    let n = {
        let mut c = &mut b;
        let mut n = &*c;  // ---------------> let's qualified it to &'long *c
        drop(c);  // -----------------------> fails to compile
        n
    };  // ---------------------------------> c drops
    
    println!("{}", n);  // ----------------> 'long extends here at least
}

I guess this should be a bug? Has reported refuse to borrow when no other reference exists technically · Issue #110759 · rust-lang/rust · GitHub.

1 Like

This makes sense, except extending the lifetime of c causes lifetime overlapping with n. reborrowing does not move c out, shadows it instead, so the real reason is voilates the shadowing

I don't think shadowing matters here. If you're really curious, your code is indeed the case mentioned in 2094-nll - The Rust RFC Book i.e. the explanation to example#3 in reborrow constraints section:

let foo = Foo { ... };
let p: &'p mut Foo = &mut foo;
let q: &'q mut &'p mut Foo = &mut p;
let r: &'r mut Foo = &mut **q;
use(*p); // <-- This line should result in an ERROR
use(r);

The key point here is that we create a reference r by reborrowing **q; r is then later used in the final line of the program. This use of r must extend the lifetime of the borrows used to create both p and q. Otherwise, one could access (and mutate) the same memory through both *r and *p. (In fact, the real rustc did in its early days have a soundness bug much like this one.)

Because dereferencing a mutable reference does not stop the supporting prefixes from being enumerated, the supporting prefixes of **q are **q, *q, and q. Therefore, we add two reborrow constraints: 'q: 'r and 'p: 'r, and hence both borrows are indeed considered in scope at the line in question.

As an alternate way of looking at the previous example, consider it like this. To create the mutable reference p, we get a "lock" on foo (that lasts so long as p is in use). We then take a lock on the mutable reference p to create q; this lock must last for as long as q is in use. When we create r by borrowing **q, that is the last direct use of q -- so you might think we can release the lock on p, since q is no longer in (direct) use. However, that would be unsound, since then r and *p could both be used to access the same memory. The key is to recognize that r represents an indirect use of q (and q in turn is an indirect use of p), and hence so long as r is in use, p and q must also be considered "in use" (and hence their "locks" still enforced).

2 Likes

Well, I just found things are a bit subtle here. Actually your code meets the
error[E0505]: cannot move out of c because it is borrowed
not error[E0502]: cannot borrow c as mutable because it is also borrowed as immutable.

The former occurs usually in the case of owned type, as shown in its error doc E0505 - Error codes index .

So, this is a case showing the uniqueness of &mut, instead of NLL. Sorry :frowning:

First, let's consider the successful case. Rust Playground

fn main() {
    let mut a = 1;
    let mut b = &mut a; // unique access to a

    let c = &mut b; // unique access to b
    let n = &*c;    // shared access to b, through the unique access to b
    drop(c); // unique access to b, by consuming itself
} // pass

Clear, isn't it? And this tougher one: Rust Playground

    let c = &mut b; // unique access to b
    let n = &*c;    // shared access to b, through the unique access to b
    &c; // shared access to b, through the unique access to b
    n;  // shared access to b, through the unique access to b
} // pass

So, my first reply is inaccurate. My bad. The lifetime of c has acceptably reached by the end of enclosing block, covering n, instead of (shared vs exlcusive) lifetime overlapping. I though your case were Rust Playground :

    let mut c = &mut b; // unique access to b
    let n = &*c;    // shared access to b, through the unique access to b
    &mut c; // unique access to the unique access to b, with shared access ending before this line
    // You can still use c here, since it's not consumed
    n; // error[E0502]: cannot borrow `c` as mutable because it is also borrowed as immutable
} // failed

Then how to explain the error in OP? Rust Playground

    let c = &mut b; // unique access to b
    let n = &*c;    // shared access to b, through the unique access to b
    drop(c); // unique access to b, by consuming itself
    // You can't use c anymore, since it's consumed before the erroneous line.
    n; // error[E0505]: cannot move out of `c` because it is borrowed
} // failed
2 Likes

The key point is which one n borrowed, c or *c.
It should be *c I think, that is why n can be move out and c will be dropped at the end of the inner block.

Then, the 2nd question is why borrow *c forbids c from dropped by hand, while it is ok for the compiler to drop it when it goes out of scope?

I think it just a trick of compiler: if you write drop explicitly, you access it which is not allowed because n is active too.

The error does indicate c is borrowed

error[E0505]: cannot move out of c because it is borrowed

I prefer taking it as a misleading.

This is the part in NLL RFC I've already copied and pasted above: Why the code fails to compile - #4 by vague

So for the following snippet (comments are stripped) Rust Playground

    let c = &mut b;
    let n = &*c;
    drop(c);
    n; 

// error[E0505]: cannot move out of `c` because it is borrowed
 --> src/main.rs:8:10
  |
6 |     let c = &mut b;
  |         - binding `c` declared here
7 |     let n = &*c;
  |             --- borrow of `*c` occurs here
8 |     drop(c);
  |          ^ move out of `c` occurs here
9 |     n; 
  |     - borrow later used here

NLL tells you that using n must keep *c alive, and due to dereferencing a mutable reference, the supporting prefixes of *c, i.e. *c and c, both lifetimes of which should keep alive. Thus, for n: &'n &mut i32

  • *c is &'b mut i32 with a reborrow constraint 'b: 'n
  • c is &'c mut &'b mut i32 with a reborrow constraint 'c: 'n

So using n should keep c alive too, or in other words, the lifetime of c shouldn't be ended before n.

Interestingly, if we replace drop(x) with non-generic function, the error differs Rust Playground

#![allow(unused)]
fn main() {
    let mut a = 1;
    let mut b = &mut a;

    let c = &mut b;
    let n = &*c;
    f(c); // the error differs if you write `drop(c)`
    n; 
}

// for f(c): E0502: cannot borrow `**c` as mutable because it is also borrowed as immutable
// for g(c): E0502: cannot borrow `*c` as mutable because it is also borrowed as immutable
// for drop(c): E0505: cannot move out of `c` because it is borrowed

fn f(_: &mut &mut i32) {}
fn g(_: &mut i32) {}

E0502 is often the sign of lifetime overlapping as my first reply (though already deleted) says.

The uniqueness of &mut is also not observable like in OP.

Here a simpler case showing the uniqueness of &mut. Consider

fn main() {
    let mut a = 1;
    let b: &mut i32 = &mut a;
    drop(b); // or just write: b;
    &b; // error[E0382]: borrow of moved value: `b`
}

Note the error code above is E0382. E0505 can also be made if we do what OP did:

    let b: &mut i32 = &mut a;
+   let c = &b;
    drop(b); // or just write: b;
    c; // error[E0505]: cannot move out of `b` because it is borrowed

But the following compiles fine by replacing drop function with less or non generic one: Rust Playground

fn main() {
    let mut a = 1;
    let b: &mut i32 = &mut a;
    f(b); // ok
    h(b); // ok
    &b;
}

fn f(_: &mut i32){}
fn h<T>(_: &mut T){}

Note reborrowing starts to work again :slight_smile:

That performs a reborrow, not a move. E.g.

1 Like

It's difficult for me to track exactly what is being argued at various parts of this thread, but this is probably the key part.

It's a magical property of reborrows. Usually in Rust, when you

  • Have some sort of thing U
  • Borrow or reborrow through it (using lifetimes) to get an R
  • And U goes away

Then R is no longer valid. But if R is a reborrow through a U: &[mut] _, U is allowed to be implicitly dropped without invalidating R, so long as the lifetimes themselves work out. The implicit drop doesn't count as a use and Rust understands the thing being pointed at by the reborrow is still valid (since the original reference was valid).

There's no (safe) way to do that without a reborrow of a reference, e.g. with a method on a struct.


Calling drop(c) isn't the same as implicitly dropping c without using it. It's a use of c.

1 Like

The RFC is somewhat misleading, too. It is authoritative, but it is conflict with rustc,

The key point here is that we create a reference r by reborrowing **q; r is then later used in the final line of the program. This use of r must extend the lifetime of the borrows used to create both p and q. Otherwise, one could access (and mutate) the same memory through both *r and *p. (In fact, the real rustc did in its early days have a soundness bug much like this one.)

It is inaccurate. According to the text, the lifetime used for creating p and q must be extended to forbid *r, *p to access the same memory.

While, they needn't to extend. When you access the memory via *q, q is alive, the aliveness requires 'q and 'p exists. If your accessing is via *p, then only 'p is required to alive, 'q is not needed.

The key point is like uncertainty principle in quantum mechanics. You observe it, you change it. When you writing code to show the necessary of some lifetime, the code itself produces the necessity.

Because dereferencing a mutable reference does not stop the supporting prefixes from being enumerated, the supporting prefixes of **q are **q, *q, and q. Therefore, we add two reborrow constraints: 'q: 'r and 'p: 'r, and hence both borrows are indeed considered in scope at the line in question.

This is correct, but means nothing.
For the following code:

fn main() {
    let mut a = 1;
    let mut b = &mut a;

    let m = {
        let mut c = &mut b; // 'c
        let mut n = &*c;  // 'n
        n
    }; 
    println!("{}", n);
}

It is the scope lifetime of c matters, not 'c, it is not same, and what we are dropping is c.
When n moves out into main as m, 'c still there, while c, gone.

So,

    let m = {
        let mut c = &mut b; // 'c
        let mut n = &*c;  // 'n
        drop(c); 
        n
    }; 

fails because of shadowing, it is ok to drop c, but fails because of drop c makes using of c while n is reborrowing *c.

It seems you insist on the term "shadowing", so where is the shadowing?

To be clear, you're not arguing for an extension to what programs are valid, you're saying the how rustc works with the example and the RFC don't agree? If so I can walk through it later.

I think this sentence is incorrect. As I said, the lifetime of c, i.e. 'c, shouldn't end as long as n is used. It's proven as the error msg shows, which accrods with NLL RFC: Rust Playground

fn main() {
    let mut a = 1;
    let mut b = &mut a;

    let n = {
        let c = &mut b;
        let n = &*c;
        n
    };
    &b; // or write `&mut b` to show the lifetime of c indeed lasts until here
    &n;
}

// error[E0502]: cannot borrow `b` as immutable because it is also borrowed as mutable
  --> src/main.rs:10:5
   |
6  |         let c = &mut b;
   |                 ------ mutable borrow occurs here
...
10 |     &b; // or write `&mut b` to show the lifetime of c indeed lasts until here
   |     ^^ immutable borrow occurs here
11 |     &n;
   |     -- mutable borrow later used here

I don't know how to tell, but I do believe what the RFC says is not what is happening in real world, maybe my misunderstanding of the RFC text, or mine is just another understanding/expression, better, I believe, personally.

The main idea is whether n re-borrows c, or in another word, c is not allowed to drop until 'n goes out of scope, in the code snap below.

let m = {
        let mut c = &mut b; // 'c
        let mut n = &*c;  // 'n
        n
    }; 

I believe c is allow to drop, except dropping c conflicts rule of only one of mut reference is allowed to access same memory at any time. When multi ^mut ref exists, the one on the top of stack is allowed, and others is not allowed, that is the shadowing @vague.

With this, when reaches to m, why c can be dropped can be explained. And, to support, 'c is not same with the scope lifetime of c is introduced.

I don't know the name, let's call it 'scope, then 'c : 'scope holds, In most of time, maybe 'scope : 'c holds too, but not all the time. Here is the case.

Another more explicit example is here refuse to borrow when no other reference exists technically · Issue #110759 · rust-lang/rust · GitHub, updated 2 especially. The invisible life time even prevents accessing to b.

'c is not lifetime of c, lifetime of c ends at the end of containing scope or an explicit drop

That sounds like stack borrows to me.
Shadowing is used when you say "the first variable is shadowed by the second, which means that the second variable is what the compiler will see when you use the name of the variable".

c goes out of scope at the end of enclosing block, but the lifetime constraint/contract is still there.

Yes, it is formal. I don't know then have to name it with my own word.

While, the key point is that: n does not borrow c, because m is just another n, if n borrows, m borrows, then c has to keep alive out of it's scope.

Well, it's just like doing stuff via a lifetime annotated function instead of a block in OP: Rust Playground

// The lifetime connection between c and n
fn f<'a, 'c>(c: &'c mut &'a mut i32) -> &'c &'a mut i32 {
    &*c
}
{
    let n = {
        let c = &mut b;
        let n = &*c;
        n
    };
    drop(n);
}

// express the same meaning as this:
{
    let n = {
        let c = &mut b;
        f(c)
    };
    drop(n);
}

// and same as this:
{
    let n = f(&mut b);
    drop(n);
}

You have to keep &mut b alive when using n. Otherwise, invalidating &mut b by inserting &b will cause

error[E0502]: cannot borrow `b` as immutable because it is also borrowed as mutable
  --> src/main.rs:29:5
   |
28 |     let n = f(&mut b);
   |               ------ mutable borrow occurs here
29 |     &b;
   |     ^^ immutable borrow occurs here
30 |     drop(n);
   |          - mutable borrow later used here

Same with

E505 can be also reproduced by inserting drop(c): Rust Playground

error[E0505]: cannot move out of `c` because it is borrowed
  --> src/main.rs:35:10
   |
33 | fn f<'a, 'c>(c: &'c mut &'a mut i32) -> &'c &'a mut i32 {
   |          --  - binding `c` declared here
   |          |
   |          lifetime `'c` defined here
34 |     let n = &*c;
   |             --- borrow of `*c` occurs here
35 |     drop(c);
   |          ^ move out of `c` occurs here
36 |     n
   |     - returning this value requires that `*c` is borrowed for `'c`