Why nested and standalone borrower have different behavior?

In the following code , the standalone works as expected , but the nested does not. In my understanding , nested and standalone borrower should have the same behavior/semantic , help me please.

struct U(u8);
struct R<'a>(&'a U );

fn standalone() {
  let u = U(1);
  let z = U(2);
  let mut r = &u;
  r = &z;
  drop(u);
  r;           // works fine
}

fn nested() {
  let u = U(1);
  let z = U(2);
  let r = &mut R(&u);
  r.0 = &z;
  drop(u);
  r.0;        // does not work         
}

Disclaimer: This answer may not be completely correct. Please wait/ask for someone else with more experience to confirm.

Maybe the problem is not because the borrower is nested vs standalone but because of the lifetime 'a specified on the struct R.

When the borrow checker sees that lifetime, it assumes that the reference will last as long as the variable r, so it demands it remain valid as long as r is in scope. There's nothing wrong with dropping the inner value, and shadowing r, though:

fn nested() {
  let u = U(1);
  let z = U(2);
  let r = R(&u);
  drop(u);
  let r = R(&z);
  r.0;      // this is fine
}

Maybe a good way to think why is to ask: If the reference is allowed to switch at runtime, as in the original code, how can the compiler possibly guarantee it will be valid for the specified lifetime 'a?

Read the example here for more clarification, because it really seems like the same thing:
[rust-blog/common-rust-lifetime-misconceptions.md at master · pretzelhammer/rust-blog · GitHub]

I made another example , this time , there is no lifetime annotation involved , why still does not work.

fn container() {
  let u = U(1);
  let z = U(2);
  let mut r = [&u];
  r[0] = &z;
  drop(u);
  r[0];       // not work
}

Lifetimes are always involved, even if you don't type them out explicitly.

The problem in your second example, I believe, is that once you put a borrow in an array, elements of the array are constrained to have the same lifetime as that of the borrow. The compiler can't reason statically about different elements of the array w.r.t. their runtime contents, so it has to be conservative.

1 Like

^ This is right (and comes from someone who probably has more experience).
In this case, it can be fixed by just moving the drop until after the final reference.

fn container() {
  let u = U(1);
  let z = U(2);
  let mut r = [&u];
  r[0] = &z;
  r[0];       // ok
  drop(u);
  // drop(z);  // even this also works
}

Maybe have a look at Lifetimes - Rust By Example to find out more.

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.