简体   繁体   中英

Lifetime bound in Async function which is also an argument

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!

The problem is that you are using a local scoped variable here. In this case you could use a &3 directly so the compiler would make that reference ( static ) for you but bound to the variable itself that would be dropped.

use std::future::Future;

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

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

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

Playground

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 .

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.

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;
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM