Lifetime problem on `impl dyn Trait`

I have this code:

trait AsBool {
    fn as_bool(&self) -> bool;

}

impl dyn AsBool {
    pub fn is_false(&self) -> bool {
        !self.as_bool()
    }
}

struct S{}

impl AsBool for S {
    fn as_bool(&self) -> bool {
        true
    }
}

fn show(v: &dyn AsBool) {
    let b = v.is_false();
    dbg!(b);
}


fn main() {
    let s = S{};
    show(&s);
}

The compiler complains:

20 | fn show(v: &dyn AsBool) {
   |            ----------- help: add explicit lifetime `'static` to the type of `v`: `&'static (dyn AsBool + 'static)`
21 |     let b = v.is_false();
   |               ^^^^^^^^ lifetime `'static` required

For dynamic trait object, if trait has no lifetime bounds then the lifetime is inferred as 'static.

I don't want my trait is bounded by 'static, it seems too limit. but I don't know how to fix..

Change it to this:

impl<'a> dyn AsBool + 'a {
    pub fn is_false(&self) -> bool {
        !self.as_bool()
    }
}
1 Like

The + 'a part in impl<'a> dyn AsBool + 'a is confusing. This is the first time I see such syntax. Is there any documentation on this?

I found this doc:

https://doc.rust-lang.org/reference/types/trait-object.html

The full syntax for talking about a trait object is dyn TheTrait + 'a for some lifetime 'a. This means:

This value could contain a value of any type that implements TheTrait and for which it is legal to keep the trait object alive until the end of 'a.

When no lifetime is specified, there are some rules for what the default choice is. When its on its own, the default choice is 'static. When its behind a reference, the default lifetime is the lifetime on the reference.

4 Likes

@alice You help me a lot :slight_smile:

@alice
By curiosity, if I insist on using 'static lifetime for dyn AsBool. How could I update my code?

you can then change the signature of show:

- fn show(v: &dyn AsBool) {
+ fn show(v: &(dyn AsBool + 'static)) {
      let b = v.is_false();
      dbg!(b);
  }

because, indeed, the issue lies in the rules for lifetime elision for dyn traits / trait objects not meaning always the same lifetime:

impl dyn Trait /* + 'static */ { … }

fn show<'v> (v: &'v (dyn Trait /* + 'v */))

If you want to follow the 'static route, then a convenient trick is to add a : 'static super-bound to the trait's definition:

- trait AsBool {
+ trait AsBool : 'static {

That will make it so all the occurrence of dyn AsBool (with an elided lifetime) will have that lifetime resolve to 'static.

That is, it changes &'v dyn AsBool from meaning &'v (dyn AsBool + 'v) to meaning &'v (dyn AsBool + 'static)

1 Like

Thanks, @Yandros.

But I wonder what is a static dynamic trait object? Maybe, I have some misunderstanding about static lifetime that dynamic trait objects will never have a static lifetime...

To my example above, how can I create a static lifetime dynamic trait, and passing it to a dyn trait + 'static?

If a trait object is annotated with 'static, that means the value inside it is such that it would be safe to keep the value alive forever (but not that you have to keep it around forever). Mainly this means that the type cannot contain any non-static references, as it would be invalid to keep such a reference around after the reference is invalidated.

You can create it like you create any other trait object.

Can you give an example to illustrate the use of a dynamic trait object which has 'static lifetime?

I modify my code to:

static s1: S = S{};
fn main() {
    show(&s1);
}

To me, s1 has 'static lifetime, and so as &s1. But compiler reports error.

I know 'static lifetime isn't equal to static variables. From my view, the static variable exists all the time during the process running and should have the 'static lifetime. Correct me if I am wrong on the state that static variable is a subset of variables that have 'static lifetime.

I will appreciate it if you can give me an example of this.


By the way, I wonder is that possible to use dyn Trait instead of &dyn Trait as the function's parameter type?

Change fcuntion signature from fn show(v: &dyn AsBool) to fn show(v: dyn AsBool) incurs error:

21 | fn show(v: dyn AsBool) {
   |         ^ doesn't have a size known at compile-time

impl<'a> dyn AsBool + 'a {

Note that you can just do

impl dyn AsBool + '_ {

for this.

(Though impl dyn is somewhat uncommon, vs just making it another trait method with a default implementation.)

I suggest reading this section of Common Lifetime Misconceptions. Note that the title and bullet points at the beginning are misconceptions, that is, not true.

Highlights:

  • There are static variables, like your s1
    • And also literals that have been promoted to statics, like "some str"
  • There are &'static references
    • Which could be references to static variables / promoted statics
    • But could also be references to dynamically allocated and leaked memory
  • There are variables with a static bound, like T: 'static
    • These could be static/promoted variables
    • They could also be &'static references
    • They could also be dynamically created, owned values
      • Like a String or u32 or, as Alice said, most data structures that don't contain a non-&'static reference
    • The lifetime in dyn Trait + 'static is such a bound

Now back to your post.

Can you be more explicit about your code or the error? These work for example.

No, you need a &dyn Trait or a Box<dyn Trait> or some other sort of indirection, just like you do with str and [T]. Every str might have a different size (length), even though they have the same type, and so too can every dyn Trait.

3 Likes
21 | fn show(v: &dyn AsBool) {
   |            ----------- help: add explicit lifetime `'static` to the type of `v`: `&'static (dyn AsBool + 'static)`
22 |     let b = v.is_false();
   |               ^^^^^^^^ lifetime `'static` required

I forgot to add '`static' lifetime. :thinking:

Does your declaration of is_false look something like this?

impl dyn AsBool {
    pub fn is_false(&'static self) -> bool {
        //           ^^^^^^^ this part in particular
        !self.as_bool()
    }
}

If so, this is the distinction between a &'static reference (which points to something like a static variable, promoted constant, or leaked memory), or to a static bound. The &'static reference is much more restricted. Just because T: 'static doesn't mean you can create a &'static T from it. A &'static T is a reference to something that definitely lasts as long as the current thread, where as a T: 'static bound just means that the type could conceivably last that long -- but it doesn't have to.

If the compiler can't prove your data will last "forever", it won't let you safely take a &'static reference to it (or you could end up with a dangling reference). That's why the things &'static can safely reference are so restricted -- those are the things the compiler can prove last "forever".

Note also how the help is telling you to make it &'static (dyn AsBool + 'static) and not just &(dyn AsBool + 'static).

The takeaway is that you probably want & and not &'static, and that you should understand the distinction between &'static T and &T where T: 'static.

1 Like

Thanks quinedot. I indeed have some misunderstandings on 'static lifetime.