What the T && 'a get after a assign

The example you posted with an use after free is caused by something called covariance. Basically if you have two lifetimes 'long and 'short such that 'short is entirely contained in 'long, the lifetime 'long is a subtype of 'short. The question now becomes, should OutRef<&'long str> be a subtype of OutRef<&'short str>? I have annotated your example with lifetimes below:

fn main() {
    // 'long starts here
    let s1 = String::from("dynamic str"); 
    // 's starts here
    let mut s: &'long str = s1.as_str();
    // 'short starts here
    let at_s: OutRef<'s, &'short str> = OutRef::<'s, &'long str>::from(&mut s);
    {
        let s = String::from("Short-lived str");
        let s_ref: &'short str = s.as_str();
        at_s.write(s_ref); // override s with a pointer to the short-lived str
        // 'short ends here
    }
    dbg!(s); // Use after free!
    // 's ends here
    // 'long ends here
}

playground (without annotated lifetimes)

What happened here is that your type was defined in a way that makes it covariant in T, which means that even though from produced an OutRef<'s, &'long str>, that type can be assigned to a variable of type OutRef<'s, &'short str> as the lifetime is shorter.

In essense, OutRef acts like &T when it comes to subtyping of lifetimes. However OutRef allows modification, so it should really act like an &mut T instead. You can fix this by providing the correct type in PhantomData like this:

struct OutRef<'a, T : 'a> {
    ptr: ptr::NonNull<T>,
    _lifetime: PhantomData<&'a mut T>, // was &'a ()
}

This change will tell the compiler that OutRef acts like &mut T wrt. subtyping. Thus OutRef<'s, &'long str> is no longer a subtype of OutRef<'s, &'short str>, so when the compiler runs the borrow checker, it sees that the lifetime on the &str must end after the call to dbg!, so when you try to assign s_ref it will complain that the shorter lifetime is no good, and fail with this error:

error[E0597]: `s` does not live long enough
  --> src/main.rs:86:20
   |
86 |         at_s.write(s.as_str());
   |                    ^ borrowed value does not live long enough
87 |     }
   |     - `s` dropped here while still borrowed
88 |     dbg!(s);
   |          - borrow later used here

playground

You can read more about covariance in the nomicon.

3 Likes

after some digging, I realize the following line is not totally right:

let at_s: OutRef<'s, &'short str> = OutRef::<'s, &'long str>::from(&mut s);

In practice, only '_ or 'static can be applied to pos1 and pos2 of OutRef::<'pos1, &'pos2 str>
And '_ means a giving up.
e.g. the other posX with a 'static will assign 'a of the OutRef<'a, T> to 'static
if both are '_, it honors &mut s.

So, if one of pos1, pos2 gives the 'static, but &mut s has a life time smaller than 'static, compiling fails.
But both give '_, compiling will pass.

I'm not sure what you're saying. You can't actually explicitly name the lifetimes in valid Rust, but the lifetimes I listed should be correct. Using '_ is the same as just not listing any lifetime at all.

What I mean is:

fn main() {
    let s1 = String::from("dynamic str"); 

    let mut s = s1.as_str(); // failed to compile
    let mut s  = "123"; // pass compiling

    let at_s = OutRef::<&'static str>::from(&mut s);
    {
        let s = String::from("Short-lived str");
        let s_ref: &str = s.as_str();
        at_s.write(s_ref); // override s with a pointer to the short-lived str
    }
    dbg!(s); // Use after free!
}

Sure, if you use a string literal, then 'long becomes 'static. The lifetime I called 's doesn't become 'static because it's the lifetime of the variable you called s (the second one), which ends at the end of the function. The lifetime i called 'short also remains the same.

Or well, there are several valid ways to assign lifetimes. If you do this

let at_s: OutRef<&'static str> = OutRef::<&'static str>::from(&mut s);

Then you still have a compiling program, but that's because on this line:

at_s.write(s_ref);

it notices that you're calling a function on a value of type OutRef<'s, &'static str>, but you're calling it with an &'short str. Obviously this doesn't work, but it will simply convert OutRef<'s, &'static str> to its subtype OutRef<'s, &'short str> on the call-site.

1 Like

what I mean is the following code can't be compiled

let mut s = s1.as_str(); // failed to compile
let at_s = OutRef::<&'static str>::from(&mut s);

event without

at_s.write(s_ref);

it indeed from() expectes a 'static but get 's

and the lone

let at_s: OutRef<&'static str> = OutRef::<&'static str>::from(&mut s);

at_s will have a OutRef<&'s str> given &'s mut s

Yes, it fails to compile because the s variable is not a &'static str. It borrows from s1 which is dropped at the end of the function. This is the lifetime I called 'long.

Oh, I am wrong, considering the following code:

fn from (p: &'a mut T)
          -> OutRef<'a, T>

fn main ()
{
    let at_s;
    {
        let mut s: &'static str = "Static str"; 
        
        let s1 = String::from("dynamic str"); 
        // 'x
        let mut s = s1.as_str(); 
       
        at_s = OutRef::<&'static str>::from(&mut s);
    }
    dbg!(at_s); 
}

I totally confused, at_s has a lifetime 'x, meaning

fn from (p: &'a mut T)
          -> OutRef<'a, T>

'a == 'x

but, how rust knows s1 need to be 'static?

I'd like to recommend you to read the following documentation: Lifetimes - Rust By Example

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.