Why does the lifetime have to be 'static? isn't the function taking ownership?

Hello, I am new to the rust language :wave:

Here is the code I'm struggling with and I would like help (pls :pray:):

trait Com{}

struct Root{
	children: Vec<Box<dyn Com>>,
}

impl Root{
    fn add(&mut self, c: impl Com){
                    //^^^^^^^^^^^ the parameter type `T` may not live long enough
        self.children.push(Box::new(c));
    }
}

struct u;
impl Com for u{}


fn main() {
    let mut r = Root{children: vec![]};
    let a = u{};
    
    r.add(a);
    
}

The compiler suggests I add + 'static but I do not understand why.

From what I understand the function Root::add takes ownership of the c: T and then creates a Box<T> and gives it ownership of c: T. So, the c: T parameter passed lives as long as the Box<T> lives.

Where am I going wrong here?

You know you pass a type that has full ownership over the data it contains. The compiler doesn't. It sees something that implements Com. You (or potentially a user of your crate) could implement Com for a type that isn't made of owned data, like a reference or a struct with a field that contains a reference. Doing what the compiler suggests and adding + 'static adds a bound to the type which says that c must be a type with full ownership over its data (or rather not containing references that are not 'static).

Here an example of how we could pass a type (&U)—by implementing Com for &U—to your add method that wouldn't live long enough:

trait Com{}

struct Root{
	children: Vec<Box<dyn Com>>,
}

impl Root{
    fn add(&mut self, c: impl Com + 'static) {
        self.children.push(Box::new(c));
    }
}

struct U;

impl Com for U {}
impl<'a> Com for &'a U {}

fn main() {
    let mut r = Root{children: vec![]};
    let a = U;
    
    r.add(a); // works
    //r.add(&a); // doesn't work since `&'a U` may not live long enough
    
}

Playground.

4 Likes

Well, imagine that your c variable is this struct:

struct MyCom<'a> {
   my_ref: &'a u32,
}

Then, even if you put it in the box, it cannot live after the end of 'a, because its reference becomes dangling then.

Adding the 'static constraint disallows types like the above. The meaning of T: 'a is "the type T must not be annotated with lifetimes shorter than 'a", so in your case you require that T: 'static, which means that the type must not be annotated with lifetimes shorter than 'static.

7 Likes

@jofas and @alice Thank you very much for the responses, it is clear to me now.

1 Like

The short answer as to why, on a technical – almost syntactical – level comes from lifetime elision rules that make

  • Box<dyn Com> be merely a shorthand for Box<dyn Com + 'static>, but
  • impl Com is not a shorthand for anything, but just a different (weaker) requirement than impl Com + 'static,

so the impl Com type simply does not fulfill the full required (but implicit in your code) Com + 'static trait bound.

As for why that pescy – seemingly unproductive/annoying – + 'static is implicitly added to your dyn Com for you: trait object types actually always need to come with a lifetime bound, so dyn Trait must actually always be of the form dyn Trait + 'some_lifetime, and leaving out the “+ 'some_lifetime” part can only ever be a shorthand for some implicitly added one, which will end up being 'static for Box<dyn Trait>, and 'a for something like &'a dyn Trait, i.e. that’s a shorthand for &'a (dyn Trait + 'a).

As a last step of the argument, why trait object types always need to come with a lifetime bound, it’s because you can never (fully) hide a short-lived reference in a struct. If you tried to define a struct

struct Foo {
    field: &u8
}

the compiler will ask you to put a lifetime argument on the struct itself, Foo<'a>, which is necessary for the same reasons the reference type itself needed a lifetime: so that the borrow checker can properly reason about it and make sure your code is safe! It’s thus unsurprising that you cannot turn a &u8 into Box<dyn Trait> without any lifetimes on the type, either.


…talking of hiding lifetimes in a type…

FYI: In fact, trait objects do have a small “magical” power that normal structs cannot achieve: they allow you to consolidate multiple lifetimes into a common lower bound. E.g. if you have a &'a mut &'b mut &'c mut SomeType (admittedly, not the most common type :sweat_smile:, and want to put it into a struct Foo, you’ll find that you’ll need to add three lifetime parameters Foo<'a, 'b, 'c> for the 3 lifetimes 'a, 'b, 'c in &'a mut &'b mut &'c mut SomeType. And since all the lifetimes (except 'a) are invariant, you cannot solve this by coercing it into something like Cell<&'a mut &'a mut &'a mut SomeType> either… but it does work to put this kind of value into a Box<dyn SomeTrait + 'a> only mentioning the shortest of the 3 lifetimes, 'a.

Of course, as a Rust beginner, feel free to ignore any of the more “advanced” comments above – especially the last ”FYI“ paragraph –or look back at them when you learned more of the advanced Rust concept I’m touching upon here. :slight_smile:

3 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.