Lifetime GATs for oldschool async closures
โ Rust ๐ 2026-03-31 ๐ค surdeus ๐๏ธ 6Hi, I'm having a problem mixing HRTBs with (old-style) async closures.
Context:
- No syntactic way to specify GATs with where bounds in where clauses ยท Issue #95268 ยท rust-lang/rust ยท GitHub
- The Better Alternative to Lifetime GATs - Sabrina Jewson
The second resource describes a workaround for the issue described in the first resource. It allows one to specify HRTBs of the form for<'a where 'a: 'limit> through an auxiliary trait. This works for structs, but I am trying to apply this same idea to closures that return futures (playground):
use futures::future::BoxFuture;
/// Trait to enforce that 'limit : 'bounded
trait LtBound<'limit, 'bounded, _LifetimeConstraint = &'bounded &'limit ()>:
FnOnce(&'bounded Arg) -> BoxFuture<'bounded, ()>
{}
impl<'limit, 'bounded, Impl> LtBound<'limit, 'bounded> for Impl where
Impl: FnOnce(&'bounded Arg) -> BoxFuture<'bounded, ()>
{}
/// Trait to close over 'bounded
trait DelimitedFnOnce<'limit>: for<'bounded> LtBound<'limit, 'bounded> {}
impl<'limit, Impl> DelimitedFnOnce<'limit> for Impl where
for<'bounded> Impl: LtBound<'limit, 'bounded>
{
}
struct Arg;
struct Captured;
/// Function that requires the bound.
fn update_arg<'a, F>(_captured: &'a Captured, _update_fun: F)
where
F: DelimitedFnOnce<'a>,
{}
// Test: closure that closes over `&'a Captured` and passes
// it to `update_arg`. This is conceptually fine given the bound
// `F: DelimitedFnOnce<'a>` (something something coercion sites)
fn test<'a>(s: &'a Captured) {
update_arg::<'a>(s, move |_| {
Box::pin(async move {
let _s = s;
})
});
}
This fails with the error I was trying to avoid:
error: lifetime may not live long enough
--> src/lib.rs:36:9
|
34 | fn test<'a>(s: &'a Captured) {
| -- lifetime `'a` defined here
35 | s.update_arg::<'a>(move |_| {
36 | / Box::pin(async move {
37 | | let _s = s;
38 | | })
| |__________^ returning this value requires that `'a` must outlive `'static`
error: could not compile `playground` (lib) due to 1 previous error
I conceptually expect this to work because DelimitedFnOnce<'a> on update_arg basically says that our FnOnce can only be invoked with lifetimes for<'b where 'a: 'b>.
I assume the reason this doesn't work is either that type inference first derives a higher-rank type for the closure using a unification variable, and fails before even considering F: DelimitedFnOnce<'a> on the function call, or that somehow the implicit bound is lost along the way.
2 posts - 2 participants
๐ท๏ธ Rust_feed