How to add lifetime bounds for trait method returning dyn trait object?

For the following simplified code example which I omit the lifetime bounds:

trait Bar {}

trait Foo {
    fn bars(&mut self) -> &mut Vec<Box<dyn Bar>>;
    fn add_bar<T: Bar>(&mut self, bar: T) {
        self.bars().push(Box::new(bar));
    }
}

struct Foo1 {
    bars: Vec<Box<dyn Bar>>,
}

impl Foo for Foo1 {
    fn bars(&mut self) -> &mut Vec<Box<dyn Bar>> {
        &mut self.bars
    }
}

Obviously this code can't pass the compilation since Box<dyn Bar> is a syntax sugar for Box<dyn Bar + 'static> , and in add_bar method, the argument bar does not necessarily be 'static, so we get an error here:

error[E0310]: the parameter type `T` may not live long enough
 --> src/lib.rs:6:26
  |
5 |     fn add_bar<T: Bar>(&mut self, bar: T) {
  |                -- help: consider adding an explicit lifetime bound...: `T: 'static +`
6 |         self.bars().push(Box::new(bar));
  |                          ^^^^^^^^^^^^^ ...so that the type `T` will meet its required lifetime bounds

I have spent couples of hours on this code, adding various lifetime bounds, trying to solve this problem. However, I still can't make this code work.

In my thought, the bars method should have the following signature:

fn bars<'a>(&'a mut self) -> &mut Vec<Box<dyn Bar + 'a>>;

And then Foo1 should be something like

struct Foo1<'a> {
    bars: Vec<Box<dyn Bar + 'a>>,
}

impl<'a> Foo for Foo1<'a> {
    fn bars<'b>(&'b mut self) -> &mut Vec<Box<dyn Bar + 'b>> {
        &mut self.bars
    }
}

This can't pass the compilation as well, the exact reason could be here.

Now I have no idea about how to solve this problem.

I might suggest this:

trait Bar {}

trait Foo<'x> {
    fn bars(&mut self) -> &mut Vec<Box<dyn Bar + 'x>>;
    fn add_bar<T: Bar + 'x>(&mut self, bar: T) {
        self.bars().push(Box::new(bar));
    }
}

struct Foo1<'x> {
    bars: Vec<Box<dyn Bar + 'x>>,
}

impl<'x> Foo<'x> for Foo1<'x> {
    fn bars(&mut self) -> &mut Vec<Box<dyn Bar + 'x>> {
        &mut self.bars
    }
}

Adding a lifetime parameter to a trait is not normally wise but here I think it might be the right approach. It's hard to say for sure without knowing the context in which you're encountering this problem.

Playing around a bit more, this is touching on the topic of variance of trait object types, which I don't 100% understand…

2 Likes

The dyn-applicability lifetime is covariant and can apply during coercion in surprising ways. However, here we're dealing with trait objects buried too deep into a a struct to be coerced, so as far as I'm aware, the lifetime is invariant behind the &mut here as per normal.

So e.g. this doesn't work:

trait Foo<'x> {
    fn bars(&mut self) -> &mut Vec<Box<dyn Bar + '_>>;
}

impl<'x> Foo<'x> for Foo1<'static> {
    fn bars(&mut self) -> &mut Vec<Box<dyn Bar + '_>> {
        &mut self.bars
    }
}

An associated type might make this work without a lifetime on the trait, though add_bar could end up less capable or less ergonomic. Not enough context for me to have an opinion on the best direction, though. In general this question looks like an attempt to work around the lack of "fields in traits".

1 Like

Because of the invariance, this signature would require borrowing self for the entire lifetime of the contents of the Vec, rendering it (all of self) unusable except via the returned reference; it also would make the trait impossible to implement for any type with a Drop implementation.

I.e. this is, slightly indirectly, the &'a mut Self<'a> anti-pattern. [1] So you don't want this one.


  1. It doesn't matter that the returned &mut doesn't have to be 'a, because you still need to borrow self for 'a to get there. ↩︎

1 Like