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?
From the Rust Reference on Trait and lifetime bounds:
T: 'a
means that all lifetime parameters ofT
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.