Lifetime 'static confusion

Simplified version

trait Trait1<T> {
    fn trait1(self: Box<Self>) -> Box<dyn Trait2<T>>;
}
trait Trait2<T> {
    fn trait2(self: Box<Self>, _: T) -> Box<dyn Trait1<T>>;
}

struct Struct<'a, T>(&'a T);

impl<'a, T> Trait1<T> for Struct<'a, T> {
    fn trait1(self: Box<Self>) -> Box<dyn Trait2<T>> {
        Box::new(Struct(self.0)) // error: lifetime may not live long enough
    }
}
impl<'a, T> Trait2<T> for Struct<'a, T> {
    fn trait2(self: Box<Self>, _: T) -> Box<dyn Trait1<T>> {
        todo!()
    }
}

The error says that returning Box::new(Struct(self.0)) requires that 'a outlive 'static. I don't know why this has to be the case but I can fix it by

trait Trait1<'a, T> {
    fn trait1(self: Box<Self>) -> Box<dyn Trait2<'a, T> + 'a>;
}
trait Trait2<'a, T> {
    fn trait2(self: Box<Self>, _: T) -> Box<dyn Trait1<'a, T>>;
}

struct Struct<'a, T>(&'a T);

impl<'a, T> Trait1<'a, T> for Struct<'a, T> {
    fn trait1(self: Box<Self>) -> Box<dyn Trait2<'a, T> + 'a> {
        Box::new(Struct(self.0))
    }
}
impl<'a, T> Trait2<'a, T> for Struct<'a, T> {
    fn trait2(self: Box<Self>, _: T) -> Box<dyn Trait1<'a, T>> {
        todo!()
    }
}

Adding 'a annotation to Trait1 and defining the return value as Box<dyn Trait2<'a, T> + 'a> with a new + 'a annotation. Problem is, this is infectious and now I have to do everywhere and everything that even touches Trait1. Even if Struct<'a, T> didn't take a reference and didn't need the explicit lifetime annotation, it would give a similar error. impl Trait1<T> for Struct<T> would complain that T may not live long enough and that I would have to assert T: 'static.

I think this is a result of lifetime elision.

You're seeing the effects of "lifetime elision". Rust has a variety of rules that exist so that you only need to write lifetimes when you're doing something clever; in this case, the rules for default trait object lifetimes kick in, and treat your:

trait Trait1<T> {
    fn trait1(self: Box<Self>) -> Box<dyn Trait2<T>>;
}

as if you had actually written:

trait Trait1<T> {
    fn trait1(self: Box<Self>) -> Box<dyn Trait2<T> + 'static>;
}

In most cases, this is what you want - it makes it easier to use trait objects, since you don't have to work out what the lifetime should be unless you're doing something that involves trait objects that need a lifetime parameter with suitable constraints.

In your original version, the 'static is elided - it's present, but Rust doesn't require you to write it out explicitly. You've now changed to write out the lifetimes explicitly, because the elided lifetime is wrong - and this is why it's become "infectious", since you've told Rust that you want to be explicit about lifetimes here.

In turn, this means that you need code like:

struct Struct2<T>(T);

impl<T: 'static> Trait1<'static, T> for Struct2<T> {
    fn trait1(self: Box<Self>) -> Box<dyn Trait2<'static, T> + 'static> {
        Box::new(Struct2(self.0))
    }
}

impl<T: 'static> Trait2<'static, T> for Struct2<T> {
    fn trait2(self: Box<Self>, _: T) -> Box<dyn Trait1<'static, T>> {
        todo!()
    }
}

so that Rust can correctly check that you're obeying all your lifetime rules - without the extra 'statics in this block, Rust doesn't know that 'a is constrained to be 'static by this implementation of Trait1 and Trait2.

2 Likes

Okay I think I understand what's going on. What does -> Box<dyn Trait2<T> + 'static> mean for 'static? In other words, what does the 'static assert? That the Box<dyn Trait2<T>> must live for the entirety of the program? Is it like asserting that reference is 'static as in &'static Something? Or is that different?

And just to be clear, I have some structs that contain inner members that are references and some that don't contain any references and own everything. All of these structs will impl Trait1. So since some of the structs have references, do I absolutely need to define Trait1 to explicitly use a lifetime of 'a in place of 'static so as to be able to impl structs that do contain references? Or is there another way out?

When 'static appears in this context it means "any lifetime in this type is (bounded by) 'static". Usually this is just trivial. It doesn't mean "lives forever." So for instance, if x is an i32 then x: 'static. References to x are a different story. When you do have a reference then the bound is meaningful. It sounds like in your situation you need the explicit lifetime.

2 Likes

The document Common Rust lifetime misconceptions explains this much better than I can.

3 Likes

You don't need to write

    fn trait1(self: Box<Self>) -> Box<dyn Trait2<'static, T> + 'static> {

You can still just write

    fn trait1(self: Box<Self>) -> Box<dyn Trait2<'static, T>> {

Like you did for fn trait2. The default dyn object lifetime varies based on syntactical context, but doesn't "switch infectiousness" when you use it once or anything like that. The default for Box<dyn Trait> outside of a function body is always 'static.

You will have to specify it (or + '_ to get more typical elision behavior) everywhere outside a function body if it can take on a lifetime other than 'static though. The rules for &dyn Trait are different but also less likely to bite.


Non-'static references? Yes, you'll need Box<dyn Trait1<...> + 'a>. It can't be 'static or you would be erasing the lifetime, at which point you could hold onto the Box until after the type-erased reference expired and then trivially cause a UAF or data race, et cetera.

This also means that in your implementation for Struct<'a, T>, the return type is going to be different for every 'a. You dealt with this in the OP by adding the lifetime to the trait, but you could also parameterize the methods instead. An associated type is another option, but it would make your boxes uglier as you'd have to specify the associated type (though a type alias may help).

As a side note, you had an unnecessary reboxing where a Box<Self> could coerce to the return type in-place.

2 Likes

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.