Box with a trait object requires static lifetime?

The code below produces a lifetime error saying T may not live long enough. After some searching I learned that adding + 'static after the T fixes the problem, but I don't understand why it's giving me an error in the first place if I'm moving T into the Box and then moving the Box into the Holder struct. Does it have something to do with Box storing the data on the heap and so that data is technically not tied to a specific lifetime?

 struct Holder {
    objects: Vec<Box<dyn Object>>,
}

impl Holder {
    fn add_object<T: Object>(&mut self, object: T) {
        self.objects.push(Box::new(object));
    }
}
2 Likes

You can write code blocks by enclosing them with triple backticks ```:

```rust
struct Holder {
    ...
```

giving:

struct Holder {
    objects: Vec<Box<dyn Object>>,
}

impl Holder {
    fn add_object<T : Object> (
        self: &'_ mut Self,
        object: T,
    )
    {
        self.objects
            .push(Box::new(object))
        ;
    }
}

Now, regarding the issue at hand, Box<dyn Trait> is just sugar for Box<dyn Trait + 'static>, meaning that the trait object / type erased element has a : 'static bound (meaning that it is not borrowing any local, and thus the value is guarantee not to dangle at any time, which makes it usable for as long as we wish. If we had, for instance, T = &'a str, then we would be borrowing a string for some lifetime 'a, after which the string could be freed, which would make using such reference / pointer unsound. So when 'a != 'static, it would be unsound to say that such a pointer lives for 'static, which is what dyn Trait + 'static says.

You can solve this in two ways:

  • either you forbid such short-lived references (recommended for the sake of simplicity). That's what adding T : 'static as the compiler suggests does. When 'a != 'static, for instance, you will not be able to use T = &'a str.

  • or you decide that you do not need such a long-lived value; that borrowing for the lifetime 'a is fine. You must then replace every occurence of Box<dyn Trait + 'static> (or the equivalent Box<dyn Trait>) by a Box<dyn Trait + 'lifetime>, by adding / infecting everything with a <'lifetime> lifetime parameter. This way, you can loosen your function into taking a T : 'lifetime for some generic 'lifetime, which will make lifetime "become" 'a when using T = &'a str:

trait Object {}

struct Holder<'lifetime> {
    objects: Vec<Box<dyn Object + 'lifetime>>,
}

impl<'lifetime> Holder<'lifetime> {
    fn add_object<T : Object + 'lifetime> (
        self: &'_ mut Self,
        object: T,
    )
    {
        self.objects
            .push(Box::new(object))
        ;
    }
}
  • Playground (compiles just fine).

  • (The first option is just the special case of this more general / generic code, whereby 'lifetime is chosen equal to 'static).

  • Note that infecting Box<dyn Trait + ... > with a 'lifetime parameter greatly reduces the benefits and thus point of using a Box (owning pointer) for the indirection: at that point, using &'lifetime dyn Trait (sugar for &'lifetime (dyn Trait + 'lifetime)) would not be that much less ergonomic, and would avoid needing to Box things around (playground).

25 Likes

thank you for the code block tip and for the answer. :slight_smile:

This topic was automatically closed 90 days after the last reply. New replies are no longer allowed.