Lifetime mismatch in async function parameter

After reading this (lifetimes-with-async-function-parameters),

I have no idea how to solve this (playgrould):

use std::future::Future;
// same as: async fn bar(_: &mut u8) { ... }
fn bar<'a>(num: &'a mut u8) -> impl Future<Output = ()> + 'a {
    async move { println!("{num:?}"); }
}
fn foo<Fut>(bar: fn(&mut u8) -> Fut) {
    let mut a = 0;
    bar(&mut a);
}
fn main() {
    foo(bar);
}

Got this error:

error[E0308]: mismatched types
  --> src/main.rs:13:9
   |
13 |     foo(bar);
   |     --- ^^^ one type is more general than the other
   |     |
   |     arguments to this function are incorrect
   |
   = note: expected fn pointer `for<'a> fn(&'a mut u8) -> _`
                 found fn item `for<'a> fn(&'a mut u8) -> impl Future<Output = ()> + 'a {bar}`
   = note: when the arguments and return types match, functions can be coerced to function pointers

How do I express the lifetime ? Because this is valid (not yet):

fn foo(bar: for<'a> fn(&'a mut u8) -> impl Future<Output = ()> + 'a) { ... }

What is the solution ?

I didn't find a good way to syntactically express the HRTB "for each lifetime 'a, accept a parameter of type &'a mut u8 and return a future capturing the same lifetime 'a".

I was, however, able to make it compile by introducing a concrete type, that is Box<dyn Future>, which can have a lifetime parameter that can be substituted in a for<'_>… clause: Playground.

use futures::future::BoxFuture;

fn bar<'a>(num: &'a mut u8) -> BoxFuture<'a, ()> {
    Box::pin(async move { println!("{num:?}"); })
}

async fn foo<F>(bar: F)
where
    for<'a> F: FnOnce(&'a mut u8) -> BoxFuture<'a, ()>,
{
    let mut a = 0;
    bar(&mut a).await;
}
2 Likes

Here is a workaround that avoids dynamic dispatch and heap allocation. It uses a custom trait in order to sneak in the + 'a constraint, by replacing the syntactically generic-parameter-like return type of function traits with a real associated type:

trait MyFunc<'a, A, T> {
    type Future: Future<Output = T> + 'a;

    fn call(self, arg: A) -> Self::Future;
}

impl<'a, A, T, R, F> MyFunc<'a, A, T> for F
where
    F: FnOnce(A) -> R,
    R: Future<Output = T> + 'a,
{
    type Future = R;

    fn call(self, arg: A) -> Self::Future {
        self(arg)
    }
}

fn bar<'a>(num: &'a mut u8) -> impl Future<Output = ()> + 'a {
    async move { println!("{num:?}"); }
}

async fn foo<F>(bar: F)
where
    for<'a> F: MyFunc<'a, &'a mut u8, ()>,
{
    let mut a = 0;
    bar.call(&mut a).await;
}

So this doesn't allocate, but now you have to write func.call(arg) instead of func(arg).

2 Likes

Here’s how the solution from the thread you linked could be used in your code: playground.

This approach is essentially the same as the second solution @H2CO3 posted above.


And unsurprisingly, you can just as well write bar as an async fn

async fn bar(num: &mut u8) {
    println!("{num:?}");
}
1 Like

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.