ysimonson
ysimonson

Reputation: 1102

Trait object lifetime that matches a returning iterator's lifetime triggers a lifetime error

I have a trait with a function that returns Box<dyn Iterator<...>>. As I understand it, based off the definition, the lifetime of this returned iterator needs to match the trait object's. But in a function that drops both at the end, I get a lifetime error. Here's a minimal example, with assumptions added as comments:

trait Foo<'a> {
    // The returned iterator's lifetime should match self's
    fn bar(&'a self) -> Box<dyn Iterator<Item = usize> + 'a>;
}

struct FooImpl {
    values: Vec<usize>
}

impl<'a> Foo<'a> for FooImpl {
    fn bar(&'a self) -> Box<dyn Iterator<Item = usize> + 'a> {
        Box::new(self.values.iter().cloned())
    }
}

fn foo_bar_caller<'a>(foo: Box<dyn Foo<'a>>) {
    // `foo` is moved into this fn
    let _iter = foo.bar();
    // `_iter` and `foo` should drop
}

fn main() {
    let foo = FooImpl { values: vec![1, 2, 3, 4, 5] };
    foo_bar_caller(Box::new(foo));
}

(rust playground with the code.) Running this gives the following lifetime error:

error[E0597]: `*foo` does not live long enough
  --> src/main.rs:17:17
   |
16 | fn foo_bar_caller<'a>(foo: Box<dyn Foo<'a>>) {
   |                   -- lifetime `'a` defined here
17 |     let _iter = foo.bar();
   |                 ^^^^^^^^^
   |                 |
   |                 borrowed value does not live long enough
   |                 argument requires that `*foo` is borrowed for `'a`
18 |     // `_iter` and `foo` should drop
19 | }
   | - `*foo` dropped here while still borrowed

For more information about this error, try `rustc --explain E0597`.
error: could not compile `playground` due to previous error

Upvotes: 1

Views: 56

Answers (1)

EvilTak
EvilTak

Reputation: 7579

You probably meant to make the function Foo::bar generic over the reference lifetime, not the trait Foo:

trait Foo {
    // The returned iterator's lifetime should match self's
    fn bar<'a>(&'a self) -> Box<dyn Iterator<Item = usize> + 'a>;
}

By putting the lifetime on the trait Foo, you are unnecessarily (in this context, at least) restricting the lifetimes of the references you can use with a particular Foo::<'_>::bar implementation. A <T as Foo<'a>>::bar (T in the context of foo_bar_caller being Box<dyn Foo<'a>>) call can only take references with the lifetime 'a, but as @MaximilianBurszley points out in the comments, what you are actually passing to it is a temporary borrow that does not have the same lifetime 'a.

Playground

Note that you can make foo_bar_caller work with your existing code (with the trait Foo being generic over the lifetime) if you make the function take a reference with the same lifetime:

fn foo_bar_caller<'a>(foo: &'a dyn Foo<'a>) {
    // `foo` is moved into this fn
    let _iter = foo.bar();
    // `_iter` and `foo` should drop
}

fn main() {
    let foo = FooImpl { values: vec![1, 2, 3, 4, 5] };
    foo_bar_caller(&foo);
}

Alternatively, you can use a higher-ranked trait bound (HRTB) to specify that foo_bar_caller must take in a value that implements Foo<'b> for all lifetimes 'b:

fn foo_bar_caller(foo: Box<dyn for<'b> Foo<'b>>) {
    // `foo` is moved into this fn
    let _iter = foo.bar();
    // `_iter` and `foo` should drop
}

This allows the Foo<'_> implementation for the value foo to be valid for the lifetime for the temporary borrow created during the foo.bar() call (since it implements Foo<'_> for all lifetimes).

Playground for HRTB version

Upvotes: 3

Related Questions