Yet another lifetime problem ?!

Hi Rustaceans,
here's another thread about lifetime problems.
I have the following code:

/// Stores information about how a type must be converted into bytes
/// This info is retrieved at runtime!
struct SomeInfo;

/// Trait to group different rust data types in a single vector,
/// since in the end only the bytes matter.
trait Foo {
    fn into_buf(&self, info: &SomeInfo) -> Vec<u8>;
}

// Implementations of `Foo` on various types...

impl Foo for &str {
    fn into_buf(&self, _info: &SomeInfo) -> Vec<u8> {
        // converting to bytes depending on `info` omitted here
        self.as_bytes().into()
    }
}

impl Foo for f64 {
    fn into_buf(&self, _info: &SomeInfo) -> Vec<u8> {
        // converting to bytes depending on `info` omitted here
        self.to_le_bytes().try_into().unwrap()
    }
}

// and many more...

/// Put the trait element list in a wrapper for convenience.
struct Bar {
    args: Vec<Box<dyn Foo>>,
}

impl Bar {
    fn new() -> Self {
        Self { args: Vec::new() }
    }
    fn add(mut self, arg: impl Foo) -> Self {
        self.args.push(Box::new(arg));
        self
    }
    // yes we can implement the `IntoIterator` trait instead -
    // this is just a dirty and quick solution
    fn into_iter(self) -> std::vec::IntoIter<Box<dyn Foo>> {
        self.args.into_iter()
    }
}

fn get_the_info_from_somewhere() -> SomeInfo {
    SomeInfo
}

/// Some type that executes something and relies on the trait elements list...
struct FooBar {}

impl FooBar {
    fn new() -> Self {
        Self {}
    }
    fn execute(&self, bars: Bar) {
        for foo in bars.into_iter() {
            // a lot of other function calls here, which will gather
            // runtime information to parse the buffers
            let info = get_the_info_from_somewhere();

            let buf = foo.into_buf(&info);
            println!("{buf:?}");
        }
    }
}

fn main() {
    let bar = Bar::new().add("something").add("another").add(542.2f64);

    let foo_bar = FooBar::new();
    foo_bar.execute(bar);
}

Compiling this will result in the following error:

error[E0310]: the parameter type `impl Foo` may not live long enough
  --> src/main.rs:39:24
   |
39 |         self.args.push(Box::new(arg));
   |                        ^^^^^^^^^^^^^ ...so that the type `impl Foo` will meet its required lifetime bounds
   |
help: consider adding an explicit lifetime bound...
   |
38 |     fn add(mut self, arg: impl Foo + 'static) -> Self {
   |                                    +++++++++

For more information about this error, try `rustc --explain E0310`.

The hint of the compiler, to restrict Impl Foo to be 'static, doesn't help me here, since I want to use references as well in calls to Bar::add().
How can I solve this problem? I tried to add a marker of type PhantdomData<'a> to the Bar struct, and then add a longer lifetime 'b: 'a to the add() function to tell the compiler, the impl Foos live longer than a Bar instance itself, but it didn't work. I am a little bit lost here right now.

Best regards 0xBIT

dyn Foo has an implicit 'static bound. If you change every instance of dyn Foo to dyn Foo + 'a (adding the 'a lifetime parameter to Bar and the impl block of Bar) the example code you posted compiles:

struct Bar<'a> {
    args: Vec<Box<dyn Foo + 'a>>,
}

impl<'a> Bar<'a> {
    // ..
    fn add(mut self, arg: impl Foo + 'a) -> Self {
    // ..
    fn into_iter(self) -> std::vec::IntoIter<Box<dyn Foo + 'a>> {
    // ..
}

edit: I tried finding the part of the book where this is covered, but I couldn't find it...it's in the reference (https://doc.rust-lang.org/reference/lifetime-elision.html#default-trait-object-lifetimes) but I feel like it should probably at least get a sentence mentioning it in the book (assuming I didn't just miss it)? Or maybe the compiler should be able to recognise that there's an implied static bound somewhere and show it in the error to explain why it wants the argument to be 'static here. Either way the current situation seems pretty confusing for new Rust developers.

5 Likes

Thanks, it works indeed! I will also take a look at the reference to get a little bit more details about it, but the solution is understandable nonetheless.

1 Like

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.