Home > Mobile >  Lifetime bound in Async function which is also an argument
Lifetime bound in Async function which is also an argument

Time:12-29

I'm trying to pass an async function as an argument. The async fucntion accepts a reference as it's arguemnt.

use std::future::Future;

async fn f(x: &i32) -> i32 {
    todo!()
}

async fn g<F, Fut>(f: F)
where
    F: Send   Sync   'static   for<'a> Fn(&'a i32) -> Fut,
    Fut: Future<Output = i32>   Send   Sync,
{
//    let x = 3;
//    f(&x).await;
}

#[tokio::main]
async fn main() {
    g(f).await;
}

But I've got compile error.

error[E0308]: mismatched types
  --> src/main.rs:18:5
   |
18 |     g(f).await;
   |     ^ lifetime mismatch
   |
   = note: expected associated type `<for<'_> fn(&i32) -> impl Future<Output = i32> {f} as FnOnce<(&i32,)>>::Output`
              found associated type `<for<'_> fn(&i32) -> impl Future<Output = i32> {f} as FnOnce<(&'a i32,)>>::Output`
   = note: the required lifetime does not necessarily outlive the empty lifetime
note: the lifetime requirement is introduced here
  --> src/main.rs:9:55
   |
9  |     F: Send   Sync   'static   for<'a> Fn(&'a i32) -> Fut,
   |                                                       ^^^

For more information about this error, try `rustc --explain E0308`.
error: could not compile `test-async-tokio` due to previous error

Why lifetime is introduced by Fut here? How to specify the lifetime bound for this code?

Best regards!

CodePudding user response:

From further testing, it appears that my original suggestion of changing the 'a lifetime param from a for<'a> clause on the trait bound to a proper generic parameter causes the the compiler to think that the lifetime exists within the returned future, which prevents locals from being used. This seems to be the case even if I explicitly apply the 'a lifetime bound to Fut and await the result.

I'm not entirely certain why the trait bounds you have established are not working, but I believe it is somehow due to async functions returning impl Future rather than a known concrete type. I was running into this issue in a few deviations from your original code. This source seems to be a solution to your problem, and I've included below a modified example for your specific use case. Note: I renamed the f param to y to emphasize that it is not calling the f function directly.

This solution adds a new trait (with blanket impl) that can be used as a trait bound for F directly, with a for<'a> clause needed if the input/output is a reference. I could be mistaken, but it seems like this works because the unknown concrete future type is wrapped up into the new trait as an associated type, so g and its trait bounds don't need to worry about it directly.

use std::future::Future;

trait AsyncFn<T>: Fn(T) -> <Self as AsyncFn<T>>::Fut {
    type Fut: Future<Output = <Self as AsyncFn<T>>::Output>;
    type Output;
}
impl<T, F, Fut> AsyncFn<T> for F where F: Fn(T) -> Fut, Fut: Future {
    type Fut = Fut;
    type Output = Fut::Output;
}

async fn f(_: &i32) -> i32 {
    todo!()
}

async fn g<F>(y: F) where F: for<'a> AsyncFn<&'a i32, Output = i32> {
    let x = 3;
    let res = y(&x).await;
}

#[tokio::main]
async fn main() {
    g(f).await;
}

  • Related