Why does `Box<dyn Trait>` introduce a `static lifetime?

I want to return a trait implementation from a method. However, I'm getting an unexpected error when the trait is implemented via a Box<dyn Trait> as shown in this minimal example. When the box on line 10 is removed then this compiles fine. When the box on line 10 is changed to a Box<Name<_>> then it also compiles fine.

So I'm trying to understand using a Box<dyn Trait> specifically causes a `static lifetime.

struct Person {
    name: String,
}

struct Name<T>(T);

impl Person {
    fn info<'a>(&'a self) -> impl Info + 'a {
        let name = Name(&self.name);
        let name: Box<dyn Info + 'a> = Box::new(name);
        name
    }
}

trait Info {
    fn info(&self) -> &str;
}

impl Info for Name<&String> {
    fn info(&self) -> &str {
        &self.0
    }
}

impl Info for Box<dyn Info> {
    fn info(&self) -> &str {
        self.as_ref().info()
    }
}

(Playground)

Errors:

   Compiling playground v0.0.1 (/playground)
error: lifetime may not live long enough
  --> src/lib.rs:11:9
   |
8  |     fn info<'a>(&'a self) -> impl Info + 'a {
   |             -- lifetime `'a` defined here
...
11 |         name
   |         ^^^^ returning this value requires that `'a` must outlive `'static`

error: could not compile `playground` (lib) due to 1 previous error

It's the default for Box, because it usually doesn't make sense to have a long-lived owning type tied to a temporary lifetime.

You can change it by using explicit Box<dyn Trait + 'a>.

If you're using trait objects tied to temporary data, try using &dyn Trait instead, which is about as restrictive, but avoids heap allocation.

dyn Trait will default to 'static unless:

  • you're using it with a reference, i.e. &'a dyn Trait, in which case the lifetime is 'a
  • you're using it with a trait containing 'a, i.e. dyn Trait<'a>
3 Likes

The point is: the underlying static type of a dyn Trait is not known to the compiler (or anyone else). Thus, it has to have an explicit lifetime annotation, so the compiler can still perform lifetime checking on it.

The elision rules of the lifetime of a trait object are roughly as follows:

  • if behind a reference, then it inherits the lifetime parameter of the reference (ie., &dyn Trait is really &'a dyn Trait + 'a)
  • if inside a wrapper with no lifetime annotations, then it depends on the context:
    • in a local context (ie., function body), the lifetime will be inferred from the use, and it will be shorter than 'static if possible
    • in a global context (eg. UDT definition, type alias, etc.), there's nothing to infer it from, so it by necessity defaults to the only named lifetime, 'static.

Thus, the static here is nothing fundamental. It's just that there isn't anything else that it could be. The alternative would be to always require a type annotation and never infer the static lifetime, instead issuing a compiler error when it can't be inferred and it's not explicitly spelled out. This would make code noisier but IMO clearer.

1 Like

I get it, and now also understand the error better.

As per the lifetime elision reference, dyn Trait will indeed default to 'static. And therefore I do need to use Box<dyn Trait + 'a>. What left me confused is that is exactly what I did on line 10.

However, I did not do it for the impl Trait for Box<dyn Trait>. So this still defaulted to 'static. Hence the error message: the return type has an implicit Box<dyn Trait + 'static> since that matches my trait impl. But I already specified the lifetime must be 'a, which name therefore has, thus the error that this 'a cannot outlive 'static.

The fix is to add 'a to the trait impl as well:

impl<'a> Info for Box<dyn Info + 'a> {
    fn info(&self) -> &str {
        self.as_ref().info()
    }
}
3 Likes

Note that you'll often see this written with an anonymous lifetime¹ '_:

impl Info for Box<dyn Info + '_> {

This means exactly the same thing as what you wrote, with the added restriction that the lifetime can't appear within the trait body itself.


Also, if you need Box<dyn Info + '_> to implement Info, it's quite common to write it generically for all boxes instead:

impl<T:Info + ?Sized> Info for Box<T> {
    fn info(&self) -> &str {
        T::info(self)
    }
}

¹ I don't know what the official name for this is.

4 Likes

Haha, yes, that's better. I learned this recently and can't believe I forgot about it. According to that reference link it is called the placeholder lifetime.

That makes dyn Trait<'a> + '_ not meet the 'static bound,[1] but does not make the default trait object lifetime non-'static on its own.

There's more details here and in the issue linked later in this comment.

Although I don't think it mattered for your OP, be aware that page is quite inaccurate.

"Wildcard lfietime."


  1. unless 'a (and the trait object lifetime) is 'static ↩︎

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