How to teach the compiler that AsyncFnMut::CallRefFuture is Send for all lifetimes up to an upper bound?
⚓ Rust 📅 2026-03-09 👤 surdeus 👁️ 1Here is a trait for something that can be fed a sequence of elements of type E, doing async work for each that works fine in a multi-threaded polling environment, and an implementation for thread-safe async closures that accept E:
/// An object that can be called serially with elements of type E, where each
/// element may require some async processing in a multi-threaded environment.
trait Acceptor<E> {
fn accept(&mut self, element: E) -> impl Future<Output = ()> + Send;
}
// We can implement this for async functions of the appropriate signature.
impl<E, F> Acceptor<E> for F
where
F: AsyncFnMut(E),
for<'a> F::CallRefFuture<'a>: Send,
{
fn accept(&mut self, element: E) -> impl Future<Output = ()> + Send {
self(element)
}
}
This works fine for async closures that are 'static:
async fn use_static() {
use_acceptor(async |e: i32| {}).await;
}
However, not with ones that do capture references (playground):
async fn use_non_static() {
{
let mut elements: Vec<i32> = Vec::new();
use_acceptor(async |e: i32| {
elements.push(e);
})
.await;
}
}
error[E0597]: `elements` does not live long enough
--> src/lib.rs:34:18
|
34 | use_acceptor(async |e: i32| {
| _____- ^
| |__________________|
35 | || elements.push(e);
36 | || })
| ||_____^- argument requires that `elements` is borrowed for `'static`
| |______|
| borrowed value does not live long enough
37 | .await;
38 | }
| - `elements` dropped here while still borrowed
|
note: due to a current limitation of the type system, this implies a `'static` lifetime
--> src/lib.rs:16:5
|
16 | for<'a> F::CallRefFuture<'a>: Send,
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
How do I make this work?
The error is similar to the one discussed in Rust issue 153558, where eggyal@ explained that the HRTB quantifier goes all the way up to 'static. But the example there is too simplistic and the workaround of not using an HRTB doesn't directly apply here because there is otherwise no lifetime to use for the Send bound for the future on the trait impl. I think what I need is something like the following to bound the quantifier:
-
A lifetime parameter
'aonAcceptor, and another parameter'bonacceptwith'a: 'band the callee accepted as&'b self. -
A bound on the trait impl like
for<'b where 'a: 'b> F::CallRefFuture<'b>: Send
But the latter is not valid syntax, and I can't figure out a way to teach the "upper bound" for the quantifier to the compiler. I tried arranging this using a struct that takes two lifetime parameters and has the bound between them like std::thread::Scope does, but I couldn't find a way to make it actually work where the compiler can still understand that the future returned by F is Send when fed an appropriate lifetime.
3 posts - 2 participants
🏷️ Rust_feed