Lifetime of de-referent of a reference is limited?

Ok, I tried to challenge rust and failed.
But I don't know why *c = *d cause the error:

cast requires that 'a must outlive 'b

I learnt from @H2CO3 that re-borrowing something behind a reference the resulted reference's lifetime is limited to the origin reference, Is deference follows the same rule?

trait Who {
    fn who(&self) -> &'static str;
}

trait A: Who {
    fn A(&self) {
        println!("A: {}", self.who());
    }
}

trait B: A {
    fn B(&self) {
        println!("B: {}", self.who());
    }
}

struct C;
struct D;

impl A for C {}
impl B for C {}
impl Who for C {
    fn who(&self) -> &'static str {
        "C"
    }
}

impl A for D {}
impl B for D {}
impl Who for D {
    fn who(&self) -> &'static str {
        "D"
    }
}

fn main() {
    fn test<'a, 'b: 'a>(c: &'a mut &'b mut dyn A, d: &'a mut &'b mut dyn A) {
        *c = *d;
    }

    let mut c = &mut C as &mut dyn A;
    let mut d = &mut D as &mut dyn A;
    test(&mut c, &mut d);
    c.A();
}

To pass the code, the lifetime annotation can be this:

    fn test<'b>(c: &mut &'b mut dyn A, d: &'b mut &'b mut dyn A){
    // and due to &'a &'b implies 'b: 'a, you can reduce it to
    // fn test<'b>(c: &mut &'b mut dyn A, d: &'b mut &mut dyn A) {
        *c = *d;
    }

This means to keep the value that &mut &'b mut dyn A refers to valid, you need to keep the d (ie. &'b mut &mut dyn A) valid.

And simpler solution is:

    // because &'b mut &'b mut T essentially means &'b mut T
    fn test_ok<'b>(c: &mut &'b mut dyn A, d: &'b mut dyn A){ 
        *c = d;
    }
    test_ok(&mut c, d); // note: `d` intead of `&mut d`

    // or
    std::mem::replace(&mut c, d);
1 Like

I believe I found the reason:

Because &mut T is not Copy, to make *c = *d compiled, move *d out. while, it is impossible to move it out behind the reference, the only left method is to re-borrow *d, e.g. *c = &*(*d).

While, *d is behind &'a T , so &*(*d) : 'a.

On the other side, *c doesn't need a moving out, re-borrowing is not needed, as a result, *c: 'b.

But what if I force re-borrow *c?

    fn test<'a, 'b: 'a>(c: &'a mut &'b mut dyn A, d: &'a mut &'b mut dyn A)
    {
        *(&mut (*c)) = *d;
    }

Sadly, It can't be compiled neither.....

OK, it maybe because &mut (*c) is just another T: 'a, *T still does not need a moving out.

test can't be allowed to compile because it makes *c and *d two mutable references to the same thing.

fn test<'a, 'b: 'a>(c: &'a mut &'b mut Option<Box<i32>>, d: &'a mut &'b mut Option<Box<i32>>) {
    //*c = *d;
}

fn main() {
    let mut c = &mut Some(Box::new(10));
    let mut d = &mut Some(Box::new(4));
    test(&mut c, &mut d);
    // now c and d are mutable references to the same thing (4)
    let x = d.as_ref().unwrap(); // x is a reference to 4
    *c = None; // drop the box. 4 has been deallocated
    dbg!(x); // UB (dereferencing a dangling reference)
}

This compiles, because the rules of the language says it has to. But the comments show that, if you could uncomment *c = *d, it would lead to obvious UB.

The other suggestion of using &'b mut &'b mut ..., allows test to compile, but moves the error to main. Using the same lifetime makes it visible in the body of main that *c and *d refer to the same thing (and the error message will tell you that *d is being borrowed when you use *c).

Note that the problem with multiple mutable references does not only happen with Box and Option; the same problem would be present with a bare i32 (or dyn A as the case may be), but the reasons for that are a little harder to see in a trivial example.

1 Like

But the point about OP is the lifetime annotation which acts like a contract between the compiler and the code you write.

That's a violation of the contract, and the compiler is yelling at you:
you made a promise to keep &'b mut T valid, but you invalidate it somewhere.

So the one of the contracts here is to hold &'b mut Option<Box<i32>> in d as long as &'b mut Option<Box<i32>> in c is alive, and vice versa. The violation can be this:

-let mut d = &mut Some(Box::new(4));
+let mut d_opt = Some(Box::new(4));
+let mut d = &mut d_opt;
test(&mut c, &mut d);

+let _ = &mut d_opt; // invalidate `d`
+drop(d_opt); // or invalidate `d_opt` thus `d`
*c = None; // err because your code doesn't obey the lifetime annotation you declare
1 Like

You can also try to get what the error msg is telling you and find a solution:

-fn test<'a, 'b: 'a>(c: &'a mut &'b mut dyn A, d: &'a mut &'b mut dyn A) {
+fn test<'b, 'a: 'b>(c: &mut &'b mut dyn A, d: &'a mut &'b mut dyn A) {
    *c = *d;
}

And you'll understand the following allows your code pass if you really understand the code comments I write in the first reply.

fn test<'b, 'a: 'b>(c: &mut &'b mut dyn A, d: &'a mut &'b mut dyn A) {}
fn test<'b>(c: &mut &'b mut dyn A, d: &'b mut &'b mut dyn A) {}
fn test<'b>(c: &mut &'b mut dyn A, d: &'b mut &mut dyn A) {}
fn test<'b>(c: &mut &'b mut dyn A, d: &'b mut dyn A){}

But you might think this would compile:

fn test<'a, 'b>(c: &'a mut &'b mut dyn A, d: &'a mut &'b mut dyn A) 
    where 'a: 'b, 'b:'a {
    *c = *d;
}

Well, that sucks. Because &mut d is invalid at the end of test call, and you can't use c anymore. So the compilation error[1]

test(&mut c, &mut d);
c.A();

error[E0502]: cannot borrow `*c` as immutable because it is also borrowed as mutable
  --> src/main.rs:45:5
   |
44 |     test(&mut c, &mut d);
   |          ------ mutable borrow occurs here
45 |     c.A();
   |     ^^^^^
   |     |
   |     immutable borrow occurs here
   |     mutable borrow later used here

indicates you break the lifetime annotation contract :slight_smile:


  1. Comment the line c.A(); lets the code pass though: the contract is obeyed. ↩︎

surely &mut d is valid at then end of test call.
cat't call c.A() only because the reborrow of &mut c, &mut d have same lifetime with c, witch tell the borrow checker &mut c, &mut d is alive given c, d is alive.

c.A() is not allowed because &mut c, &mut d is still valid so c is not allowed to access

There is no difference with

&'a mut &'b mut dyn A means 'b : 'a, and test<'b, 'a: 'b> made 'a: 'b, only 'a == 'b satisfies both

Did you notice the c: &mut &'b mut dyn A, which is not c: &'a mut &'b mut dyn A. That's the main point for your problem.

c: &mut is not important, it can be any '_, given 'b: '_

In fact, this version interestingly makes the code working,

fn main() {
    fn test<'b>(c: &mut &'b mut dyn A, d: &'b mut &'b mut dyn A) {
        *c = *d;
    }
    
    let mut c = &mut C as &mut dyn A;
    let mut d = &mut D as &mut dyn A;
    let dp = &mut d;
    test(&mut c, dp);
    c.A();
}

Sadly I can't access dp after test, the safety of rust keeps because dp and d: &'b mut &'b mut dyn A are both alive, but only d: &'b mut &'b mut dyn A is allowed to access, which is invisible outside of test....

update:
*c = *d made c take another reborrow from d: &'b mut &'b mut dyn A, which means after test, there 3 mut reference to D

  • dp
  • d: &'b mut &'b mut dyn A
  • c

The lower hide the upper.
That is why only c.A() is granted

try fn test<'b>(c: &mut &'b mut dyn A, d: &'b mut &mut dyn A)

fn main() {
    fn test<'b>(c: &mut &'b mut dyn A, d: &'b mut &mut dyn A) {
        *c = *d;
    }
    
    let mut c = &mut C as &mut dyn A;
    let mut d = &mut D as &mut dyn A;
    let dp = &mut d;
    test(&mut c, dp);
    c.A();
    d.A();
}

Is this what you want?

(Replying to the OP)

You can never get a &'long mut by going through a &'short mut &'long mut.

In your OP, you need to modify a value (a &'b mut dyn A) you don't have ownership of. You're doing the modification in your function body, so any brief outer borrow will do:

    // Equivalent to your original signature
    fn test<'b>(c: &mut &'b mut dyn A, d: &mut &'b mut dyn A) {

But as you said, in addition to the borrowing rules, you can't move the &'b mut dyn A from d and leave nothing in its place [1]. However, everything is fine if you have something of the same type you can leave in its place. As it happens, you have such a thing: The &'b mut dyn A from c that you're trying to overwrite.

    fn test<'b>(c: &mut &'b mut dyn A, d: &mut &'b mut dyn A) {
        std::mem::swap(c, d)
    }

Playground.

If you try to "manually" swap using a temporary, you'll find that the borrow checker still doesn't allow it. swap and take and friends are particularly useful when you need to manipulate lifetime-carrying values that are underneath a shorter-lived outside &mut like we have here. They encapsulate the unsafe needed to temporarily create the invalid value and then replace it with a valid one -- to manipulate the underlying values without being limited by the outer lifetime.


(Skimming the rest of the thread)

You can't turn a &'short mut &'long mut into a &'short mut &'short mut either. Lifetimes behind &'a mut are invariant -- they can't change at all. [2] If they could, consider:

fn oops<'a>(v: &'a mut Vec<&'static str>) {
    let local = "drops at end of function".to_string();
    v.push(&*local);
}
fn main() {
    let mut v = vec![];
    oops(&mut v);
    println!("{}", v[0]);
}

If you have a &'a mut Thing<'a>, you can only use Thing<'a> through the &'a mut ever again. The inner borrow becomes only usable through the outer borrow.

This does compile to the point of being to call test:

    fn test<'b>(c: &'b mut &'b mut dyn A, d: &'b mut &'b mut dyn A) {
        *c = *d
    }

But the error is because you can never use c and d again, except through the outer &mut &muts which you created -- which you created and then gave away to test (they are not Copy). If you're going to use c and d again, it can only be indirectly now, via test giving you some sort of access back by way of those outer borrows:

    fn test<'b>(c: &'b mut &'b mut dyn A, d: &'b mut &'b mut dyn A) -> &'b mut dyn A {
        *c = *d;
        *c
    }

Playground.

And so then this one works...

    fn test<'b>(c: &mut &'b mut dyn A, d: &'b mut &'b mut dyn A) {
        *c = *d;
    }

...because

  • You can get a &'b mut dyn A from a &'b mut &'b mut dyn A [3]
  • It's okay to invalidate d here by moving from underneath the reference precisely because you can never use it again
    • You can't even use d inside test again, because you had to reborrow it for the duration of its entire lifetime to pull the inner &'b mut dyn A out
  • You only borrowed c for a shorter period, so now it's ok to use c after the call to test

  1. at least, not if there's anyway such an invalid value could be observed ↩︎

  2. The 'a on the outer &'a mut itself can still shrink; it is covariant; this is why reborrowing works. ↩︎

  3. more generally, you can get a &'short mut from a &'short mut &'long mut ↩︎

2 Likes