Questions about lifetime errors on impl trait


#1

See the code example below.

Calling bravo_impl from alpha_impl will cause lifetime error.
But you can call a trait object value function with the same lifetime.

Why?

trait IFoo{}

struct Foo{}

impl IFoo for Foo {}

fn main() {
    let foo = Foo{};
    let foo = Arc::new(foo);
    tokio::run(alpha_impl(foo));
}

fn alpha_impl(
    o: impl Deref<Target = impl IFoo> + Clone + 'static,
) -> impl Future<Item = (), Error = ()> + Send + 'static {
    // bravo_result(o) // it works
    bravo_impl(o) // lifetime error!
    // bravo_box(o) // it works
}

fn bravo_result(
    _o: impl Deref<Target = impl IFoo> + Clone + 'static,
) -> future::FutureResult<(), ()> {
    future::ok(())
}

fn bravo_impl(
    _o: impl Deref<Target = impl IFoo> + Clone + 'static,
) -> impl Future<Item = (), Error = ()> + Send + 'static {
    future::ok(())
}

fn bravo_box(
    _o: impl Deref<Target = impl IFoo> + Clone + 'static,
) -> Box<dyn Future<Item = (), Error = ()> + Send + 'static> {
    Box::new(future::ok(()))
}
error[E0310]: the parameter type `impl IFoo` may not live long enough
  --> src/main.rs:26:6
   |
25 |     o: impl Deref<Target = impl IFoo> + Clone + 'static,
   |                            ---------- help: consider adding an explicit lifetime bound `impl IFoo: 'static`...
26 | ) -> impl Future<Item = (), Error = ()> + Send + 'static {
   |      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
note: ...so that the type `impl std::marker::Send+futures::Future` will meet its required lifetime bounds
  --> src/main.rs:26:6
   |
26 | ) -> impl Future<Item = (), Error = ()> + Send + 'static {
   |      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: aborting due to previous error

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=749266b47c2cbdb930e06cf7941f4859


#2

This has to do with lifetime elision.

alpha_impl desugars to

fn alpha_impl<'a>(
    o: impl Deref<Target = impl IFoo + 'a> + Clone + 'static,
) -> impl Future<Item = (), Error = ()> + Send + 'static + 'a {
    // bravo_result(o) // it works
    bravo_impl(o) // lifetime error!
    // bravo_box(o) // it works
}

and bravo_impl desugars to

fn bravo_impl<'a>(
    _o: impl Deref<Target = impl IFoo + 'a> + Clone + 'static,
) -> impl Future<Item = (), Error = ()> + Send + 'static + 'a {
    future::ok(())
}

due to lifetime elision rules, because 'a is not guaranteed to outlive 'static, it complains. When you use bravo_implyou are propagating the lifetime, so Rust thinks that the return type of bravo_impl only can’t live for a 'static lifetime.

This is a care where annotating lifetimes is hard and easy to get wrong. Removing 'static solves your problem.

The reason this solves it is because Rust no longer needs to enforce that anything lives for a 'static lifetime.

The reason bravo_result and bravo_box worked is because all types that don’t depend on lifetimes themselves are automatically fulfill the static lifetime. So future::FutureResult<(), ()> and Box<dyn Future<Item = (), Error = ()> + Send + 'static> are both static because they both don’t depend on any lifetimes.