asynchronousrust

Why does Rust complain about the lifetime of a type whose value is never actually used?


trait What {
    fn what(&self) -> impl Future<Output = ()> + Send;
}

trait Foo {
    type T: Send + Sync + What + 'static;
}

async fn generic_operation<C: Foo>(x: &C::T) {
    x.what().await;
}

struct FooStruct<C: Foo> {
    field: C::T,
}

pub fn assert_spawnable<F>(_: F)
where
    F: Future + Send + 'static,
    F::Output: Send + 'static,
{}

async fn spawner_function<C: Foo>(x: FooStruct<C>) {
    use std::sync::Arc;
    let fields = Arc::new(x);
    assert_spawnable({
        let fields = fields.clone();
        async move {
            generic_operation::<C>(&fields.field).await;
    }});
}

This code fails to compile with the following error. (Playground)

   Compiling playground v0.0.1 (/playground)
error[E0310]: the parameter type `C` may not live long enough
  --> src/lib.rs:26:5
   |
26 | /     assert_spawnable({
27 | |         let fields = fields.clone();
28 | |         async move {
29 | |             generic_operation::<C>(&fields.field).await;
30 | |     }});
   | |       ^
   | |       |
   | |_______the parameter type `C` must be valid for the static lifetime...
   |         ...so that the type `C` will meet its required lifetime bounds
   |
help: consider adding an explicit lifetime bound
   |
23 | async fn spawner_function<C: Foo + 'static>(x: FooStruct<C>) {
   |                                  +++++++++

For more information about this error, try `rustc --explain E0310`.
error: could not compile `playground` (lib) due to 1 previous error

But I do not understand why this error arises. Value of type C is never actually constructed, nor passed around in this code. Also, there seems to be no constraint on the lifetime of C, at least explicitly. If so, why does Rust say the parameter type C must be valid for the static lifetime?


Solution

  • From the Rust Reference on Trait and lifetime bounds:

    T: 'a means that all lifetime parameters of T outlive 'a.

    Your async block needs to outlive 'static and since it captures an Arc<FooStruct<C>> it too needs to outlive 'static. It falls apart because all lifetime parameters need to outlive 'static and C may have lifetime parameters, but since C is unconstrained, it can't claim that it always outlives 'static and thus the compiler emits an error. Notice that this only involves the generic parameters and does not consider the construction of the type.

    Suggesting that the borrow checker consider lifetimes it thinks are "unused" as 'static is a questionable prospect in general. Lifetimes can convey access restrictions without actually keeping a reference around and plenty of APIs exploit the borrow-checker semantics without them.

    That being said, I struggle to think of a concrete problem here even if C were to have a lifetime. Maybe this can be relaxed in the future, or maybe there's some lurking issue that I haven't considered.


    TANGENT: You can pass some Foo<'a> as something constrained to 'static, but it can only work via a coercion to Foo<'static> and is only possible if 'a is contravariant. See subtyping and variance. This does end up looking at the type's construction to determine the variance for coercion, but that is separate from the above rule regarding constraint satisfaction; it does not mean that Foo<'a> is 'static. See this playground sample for a demonstration. This doesn't really help your case at all because you can't constrain that C is contravariant over all lifetimes - short of C: 'static as the compiler already suggested.