How to specify the lifetime of a object inside the main function

Here is the simplified version of the probelm I am facing right now

struct Token<'a>{
    slice:&'a str,
}
struct WrappedToken<'a>(&'a Token<'a>);

trait ToWrappedToken<'a>{
    fn to(&'a self)->WrappedToken<'a>;
}
impl<'a> ToWrappedToken<'a> for Token<'a>{
    fn to(&'a self)->WrappedToken<'a>{
        WrappedToken(self)
    }
}
fn to_wrap<'a>(token:Token<'a>)->Box<dyn ToWrappedToken<'a>+'a>{
    Box::new(token)
}
fn main(){
    let token=Token{slice:"It's working well."};
    let r=to_wrap(token);
    r.to();
}

Now If I run the code I get error "r is borrowed when it is destroyed,when is clearly not the case,the problem is get_first method that takes makes the lifetime of the pointer equivalent to the dyn Object which get destroyed after the main function.How can I specify that it's okay to die when the pointer dies?"

You could use lifetime elision:

struct Token<'a> {
    slice: &'a str,
}

trait GetLetter<'a> {
    fn get_first(&'_ self) -> &'a str;
}

impl<'a> GetLetter<'a> for Token<'a> {
    fn get_first(&'_ self) -> &'a str {
        &self.slice[..1]
    }
}

fn to_getletter<'a>(token: Token<'a>) -> Box<dyn GetLetter<'a> + '_> {
    Box::new(token)
}

fn main() {
    let token = Token {
        slice: "It's working well.",
    };

    let r = to_getletter(token);

    r.get_first();
}

Playground.


I highly recommend reading the Learning Rust book, i.e. the chapter about lifetime elision and the following chapters are great reading material about lifetimes.

I guess I made a mistake in my question.This is the simplified version of the problem I am facing.I can't change the trait signature.

trait GetLetter<'a> {
    fn get_first(&'a self) -> &'a str;
}

So is there a way to make my code work changing in hte main function?

A better example of the code would be something like this

struct Token<'a>{
    slice:&'a str,
}
struct WrappedToken<'a>(&'a Token<'a>);

trait ToWrappedToken<'a>{
    fn to(&'a self)->WrappedToken<'a>;
}
impl<'a> ToWrappedToken<'a> for Token<'a>{
    fn to(&'a self)->WrappedToken<'a>{
        WrappedToken(self)
    }
}
fn to_wrap<'a>(token:Token<'a>)->Box<dyn ToWrappedToken<'a>+'a>{
    Box::new(token)
}
fn main(){
    let token=Token{slice:"It's working well."};
    let r=to_wrap(token);
    r.to();
}

Can you avoid boxing and use a reference instead?

struct Token<'a> {
    slice: &'a str,
}

struct WrappedToken<'a>(&'a Token<'a>);

trait ToWrappedToken<'a> {
    fn to(&'a self) -> WrappedToken<'a>;
}

impl<'a> ToWrappedToken<'a> for Token<'a> {
    fn to(&'a self) -> WrappedToken<'a> {
        WrappedToken(self)
    }
}

fn to_wrap<'a, 'b>(token: &'b Token<'a>) -> &'b dyn ToWrappedToken<'a> {
    token
}

fn main() {
    let token = Token {
        slice: "It's working well.",
    };
    
    let r = to_wrap(&token);
    
    r.to();
}

Playground.

1 Like

Could you tell me why this code is invalid?In my project I have to workaround this using Rc for now.
like this is valid:

struct Token<'a> {
    slice: &'a str,
}

struct WrappedToken<'a>(&'a Token<'a>);

trait ToWrappedToken<'a> {
    fn to(&'a self) -> WrappedToken<'a>;
}

impl<'a> ToWrappedToken<'a> for Token<'a> {
    fn to(&'a self) -> WrappedToken<'a> {
        WrappedToken(self)
    }
}

fn to_wrap<'a>(token: &'a Token<'a>) -> &'a dyn ToWrappedToken<'a> {
    token
}

fn main() {
    let token = Token {
        slice: "It's working well.",
    };
    
    let r = to_wrap(&token);
    
    r.to();
}

but this is not

struct Token<'a>{
    slice:&'a str,
}
struct WrappedToken<'a>(&'a Token<'a>);

trait ToWrappedToken<'a>{
    fn to(&'a self)->WrappedToken<'a>;
}
impl<'a> ToWrappedToken<'a> for Token<'a>{
    fn to(&'a self)->WrappedToken<'a>{
        WrappedToken(self)
    }
}
fn to_wrap<'a>(token:Token<'a>)->Box<dyn ToWrappedToken<'a>+'a>{
    Box::new(token)
}
fn main(){
    let token=Token{slice:"It's working well."};
    let r=to_wrap(token);
    r.to();
}

Here is what I think happens, though I'd much appreciate if someone more knowledgeable about Rust's type system would proof-read this:

  1. dyn ToWrappedToken<'a> can't be coerced to a dyn ToWrappedToken<'b> where 'a: 'b, because dyn Trait<'a> is invariant in 'a. That means dyn ToWrappedToken<'a> is around for all of 'a. 'a in your case is the lifetime of the boxed trait object, because the 'static lifetime of Token::slice must be shortened to the lifetime of the box if we want to call ToWrappedToken::to on it, thanks to the &'a self argument. But due to the invariance we can't coerce WrappedToken<'a> to anything shorter than the lifetime of the box. So WrappedToken<'a> will be around when the Box is dropped. This is similar to how a &'a mut T<'a> will live for as long as the value it references.

  2. At first I was puzzled by the error message. Yes, Box<T> implements Drop, but it uses the dropck eyepatch on T, telling dropck that it doesn't touch T during destruction, allowing any references in T to dangle. I assume though that Box isn't the problem here but the trait object. How is it supposed to guarantee that none of the types that implement ToWrappedToken implement Drop in a way that 'a isn't allowed to dangle? It can't (I guess), hence the error.

  3. Using &'_ dyn ToWrappedToken<'a> seems to side-step the whole issue of dyn ToWrappedToken<'a> being invariant in 'a. Looks to me like type inference is able to deduce that 'a can be shorter than the lifetime of token.

1 Like

Pragmatically, the key difference between the two is in the function parameter here — not the details of the return value:

fn to_wrap<'a>(token: &'a Token<'a>) -> &'a dyn ToWrappedToken<'a> {
/// ... vs. ...
fn to_wrap<'a>(token: Token<'a>) -> Box<dyn ToWrappedToken<'a>+'a> {

In the first case, to_wrap is given a reference to Token, and that reference is guaranteed to be valid for 'a. In the second case, to_wrap is given ownership of the Token, which means that the Token only stays alive for as long as to_wrap() can keep it alive. The function keeps it alive by returning it (in a Box), but that isn't sufficient because the Box could be dropped before 'a ends. (And if the lifetime involved were covariant, not invariant as @jofas mentions, then that might be okay, because further usage of the Box can just retreat to using only the shorter lifetime that can be gotten by borrowing the Box, but it's invariant, which means that as soon as you get the value back from to_wrap you can't treat it as any shorter than it says it is.)

The lesson you should take from this is: while, in most cases, ownership of a value is strictly more powerful than borrowing the value, that's not true when you need to give out a longer-lived reference to the value. When you see a signature like

fn some_func<'a>(input: Token<'a>) -> ...

you should notice that the Token itself cannot be valid for 'a, because it is being moved while 'a is active. So it is not possible to construct an &'a Token<'a> (which is what your ToWrappedToken requires) with this lifetime 'a, but it might be possible to construct an &'b Token<'b> with a shorter lifetime 'b.

4 Likes

Another way

@@ -2,13 +2,13 @@ struct Token<'a>{
     slice:&'a str,

 }
-struct WrappedToken<'a>(&'a Token<'a>);
+struct WrappedToken<'a, 'tmp>(&'tmp Token<'a>);

 trait ToWrappedToken<'a>{
-    fn to(&'a self)->WrappedToken<'a>;
+    fn to<'tmp>(&'tmp self)->WrappedToken<'a, 'tmp>;
 }
 impl<'a> ToWrappedToken<'a> for Token<'a>{
-    fn to(&'a self)->WrappedToken<'a>{
+    fn to<'tmp>(&'tmp self)->WrappedToken<'a, 'tmp>{
         WrappedToken(self)
     }
1 Like

Yes, due to the invariance of trait parameters, &'a Box<dyn Trait<'a> + '_> is borrowed "forever", which is incompatible with having a non-trivial destructor involving 'a.

Yes, dyn Trait always has a non-trivial destructor from a borrow-checker perspective. Because it may indeed be true, depending on the base type.

Thus, here's a reduction of the OP (as it exists right now).

The difference is that &dyn Trait doesn't own dyn Trait so no dyn Trait is ever destructed.

The Box<_> version could be created with a shorter lifetime too, since it was created by passing a Token<'a> which is covariant in 'a. But it's not the relationship between the lifetime of the original Token<'_> and the dyn Trait<'_> which is problematic.

n.b. when I use the term "lifetime" I mean a Rust lifetime ('_) and not value liveness.

2 Likes

Thanks!

Yes, that is where I was starting to feel out of my depth. Very interesting. So we could summarize the cause of the error is the invariance in 'a of dyn Trait<'a> in conjunction with the ownership of Box and its drop glue containing the non-trivial destructor of dyn Trait<'a>, no?

Yes, that's the immediate cause of the error.

A &'a dyn Trait<'a> where the dyn Trait<'a> is created from something with a covariant lifetime is probably going to be fine (last only as long as it needs to). One created from something without a covariant lifetime will probably borrow the base type forever, but may still compile if nothing else (including a destructor) ever needs the base type.

2 Likes

While talking about what precisely triggers that error that @irfan-hossain-bhuiya is seeing have merit practically it's better to do what @vague have done and explain why such change makes sense.

Because every time I see something like struct WrappedToken<'a>(&'a Token<'a>) or fn to(&'a self)->WrappedToken<'a>; I just know that what I would see after that point is some kind of nonsense lifetime markup code which wouldn't compile.

To understand why you don't need to understand all these precise nuances much, you just do a quick glance and see code which, most likely, wouldn't work.

Because lifetime markup marks:

  1. Don't affect the generated code[1].
  2. Are, nonetheless, incredibly important.

What they are doing if they don't affect the generated code? The add formal proof of correctness to your code. And compiler just simply verifies that said proof of correctness is actually valid. That's it.

Now, suppose you are solving some math problem and use a to denote diameter of steering wheel in your car and also length of steering column and also width of the driver seat… I wouldn't expect such proof to be correct. In fact math teacher would just reject such “proof” after reading first few lines and criss-crossing everything by just adding note “steering wheel diameter and width of driver seat, may be different, please redo”.

Similarly here: talking about what kind of “insane lifetimes markup” may still work and how is an interesting intellectual exercise but for newbies the first rule they should learn is: never give identical names for lifetimes except if you have some reason to make them identical.

In that code that we had in the very first message WrappedToken had the exact same lifetime as Token that it wraps. And then to merges two lifetimes, too. Why would you do that? To make it impossible to write sane code in main? Congrats, you have achieved that.

Indeed, the very first message includes that passage:

It's like writing on the sticker “brick of width 'a, length 'a, width 'a” and then try to sell real, non-cubical, brick under such banner and complaining that no one wants to buy it and everyone tells you that you are not giving them what you promised. You told the compiler that it's not Ok to die when the pointer dies thus you are stuck with that promise. Either change the sticker or brick, there are no way to declare one thing and then sell another in Rust.


  1. There are some HRBT-related exceptions, but these could be ignored in the majority of programs. ↩︎

1 Like

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.