Lifetime GATs for oldschool async closures

โš“ Rust    ๐Ÿ“… 2026-03-31    ๐Ÿ‘ค surdeus    ๐Ÿ‘๏ธ 6      

surdeus

Hi, I'm having a problem mixing HRTBs with (old-style) async closures.

Context:

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

Read full topic

๐Ÿท๏ธ Rust_feed