How to explain the deref lifetime rule

I know the rule,

*(&'short &'long mut T) → &'short T
*(&'short &'long T) → &'long T

Well, I think *(&'short mut &'long mut T) → &'short mut T is enough for soundness.
It is not necessary to limit *(&'short &'long mut T) → &'short T.

Anyway, *(&'short &'long mut T) → &'short T at most introduce a &'long T, but does not shadow &'short T because they are shared.

Given an &'short &'long mut T, after the lifetime 'short ends, you will be able to use the &'long mut T directly. A mutable reference must have exclusive access when used directly, so if you also have &'long T references that you got from the &'short &'long mut T, then that would violate the exclusivity requirement.

1 Like

You are talking about the generated ref will lose tracking in term of stack borrow.

But in fact *(&'short &'long mut T) → &'short T is impossible, because &'long mut T is not Copy. So you can't move &'long mut T behind of &'short out.

So the real usage of such a expression is &*(&'short &'long mut T), which is another ref1 = &'a &'long mut T. e.g. the stack-borrow remains, &'long mut T can't be accessed before ref1 dies;

I think ref1 = &'long &'long mut T is Ok, it is not necessary to limit ref1 = &'short &'long mut T

Reborrowing can perform the operation:

pub fn reborrow<'short, 'long, T>(r: &'short &'long mut T) -> &'short T {
    &**r
}
1 Like

@alice I have updated the post, the old one is too simple to explain my thoughts.

The key points here is wether &'short T returned from reborrow<'short, 'long, T> can block the access to &'long mut T in the argment.

I think your answer is NO --- only &'short &'long mut T can block the access to &'long mut T, the returned &'short T is just a brother of &'long mut T.

So the reason of soundness can be upheld is that the returned &'short T has same lifetime with &'short &'long mut T which blocks the access to &'long mut T, so in the lifetime of &'short T, &'short &'long mut T lives too, makes &'long mut T inaccessibly.

But if you are right, consider

pub fn reborrow<'short, 'long, T>(r: &'short mut &'long mut T) -> &'short mut T {
    &mut **r
}

So, the returned &'short mut T can't block access to &'long mut T in the argment, either, because the returned &'short mut T is just a brother of &'long mut T.

Ok, given that, &'short mut T naturally can't block the access to &'short mut &'long mut T, because &'short mut &'long mut T is a nephew of &'short mut T.

Then, the 2 mut references, &'short mut &'long mut T and the returned &'short mut T, both can access the T, violate the exclusivity requirement.

fn reborrow_unsound<'short, 'long, T>(r: &'short &'long mut T) -> &'long T {
    unsafe { &*(*r as *const T) }
}

fn main() {
    let mut a = String::from("hello");
    let a_mut = &mut a;
    let a_ref = reborrow_unsound(&a_mut).as_str();
    
    *a_mut = String::from("world");
    
    println!("{a_ref}");
}

2 Likes

I understand finally.

It is not about soundness or not, it causes trouble for coding.

No, the returned &'short T/&'short mut T will require the &'long mut T to still be valid and be borrowed due to the &'short &'long mut T argument. And this is how it works generally, if you have a &'a U argument and return a &'a V the U will be borrowed for as long as you hold onto the &'a V.

What you said would be true instead if the function returned a &'long T/&' long mut T, which is why they are unsound and thus not allowed.

They are not unsound.

&'long T/&' long mut T would not shadow &'short &'long mut T or &'short mut &'long mut T as a whole, but does shadow the &'long mut T part. That means, you still can't do anything bad.

There is no things of

like @alice said.

The proof:

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

    let mut c = &mut b;
    let mut n1 = &mut **c;

    c = &mut a;   // Access c
    *n1 = 3;      // Access b via reborrow

    println!("{} {}", c, n1);    // OK
    // println!("{} {}", c, b);  // Not OK to access b directly
}

The reason rust disallows getting a &'long T from &'short &'long mut T is: it will be more difficult for writing code.

For example, assume we can get &'long i32 from f1:

fn f1(r: &'short &'mut long i32) -> &'long i32 {
    &**r
}

fn f2(r: &'short &'mut long i32) -> i32 {
    let _ = f1(r);
    0
}

fn main() {
    static mut a: i32 = 8;
    let mut r1 : &'static mut i32= unsafe { &mut a };

    f2(&r1);    
    *r1 = 10;  // fails here because a is still borrowd
}

That is not good.

What is considered UB is defined at the language level. Aliasing &mut is already UB in Rust. Use after free is UB in any language. @zirconium-n demonstrated a use after free scenario caused by getting a &'long _ from a &'short &'long mut _. So it's definitely unsound.

Maybe some other lifetime-using language could exist with different semantics where it is sound. That hypothetical language is not Rust.

I'm not sure what your code was trying to demonstrate, but the itrafunction borrow checker may be smarter than you think.

Oh, yes, it is a more direct proof showing I am wrong at &'long T will shadow &'long mut T during 'long is not out of scope.
I misunderstand what the code really means:

fn reborrow_unsound<'short, 'long, T>(r: &'short &'long mut T) -> &'long T {
    unsafe { &*(*r as *const T) }
}

fn main() {
    let mut a = String::from("hello");
    let a_mut = &mut a;
    let a_string = reborrow_unsound(&a_mut);
    let a_ref = a_string.as_str();

    *a_mut = String::from("world");
    println!("{a_ref}");
}

I find the unsoundness is because of current borrow checker does not shadow a_mut by a_string anymore when 'short reaches it's end, which in stack-borrows, a_mut is shadowed for 'long.

Seems because of rust forbids get a 'long T from &'short &'long mut T, So &'long mut T is shadowed only during 'short....

I am trying find a reason why rust disallows getting a &'long T from a &'short &'long mut T, whether it is impossible or for a better reason.

My final conclusion is it is possible to get a &'long T from a &'short &'long mut T technically. It disallows such a behavior because of the reason I mentioned above:

Although code of @zirconium-n shows it will bring unsoundness if I insist to break the re-borrow semantic in rust and @alice is correct at &'long mut T is not shadowed anymore by &'short &'long mut T (and by &'long T) after 'short. (Maybe I should say &'long mut T is shadowed by only &T after the reborrow because &T now is the only entrance to access T).

&mut being exclusive is a core tenet of Rust's memory safety borrowing model. A &'long T would alias the &'long mut T once 'short expires.

Not soundly, without redefining lifetimes or altering the axiomatic &mut exclusivity, etc. (What I would call "not Rust".)

In a rust-like language (which allows getting &'long T from re-borrowing &'short &'long T), only need shadow &'long mut T by &'long T during entire 'long technically to find unsound codes out, like what is done in miri (I think that is why miri can find the unsoundness).

It is not good for bringing more difficulty, though (I call it the better reason for why rust disallows this).

That's called a &'long &'long mut _.

1 Like

By the way, I feel like you're misinterpreting the "rule". There's no rule that specifically says that you can only reborrow a &'short &'long mut T as a &'short T. Instead, this holds in general for any reference. If you have a &'short (mut?) Something (where Something can be anything, even other references) then you can only get a &'short (mut?) SomethingElse out of it (and the mut may be long in the process).

&'short &'long T is the exception because &'long T is Copy, so you can copy the &'long T instead of reborrowing it. &'long mut T is not Copy (because that would trivially allow to break the borrowing rules), so the same exception doesn't apply to it.