I have the following problem:

  • I have a function foo that expects a function f as argument
  • The function f is async and takes one argument by reference

How do I resolve the lifetimes?

It's easy if f is async and takes one argument by value, like so:

use std::future::Future;

async fn foo<F, Fut>(f: F)
where
    F: 'static + FnOnce(String) -> Fut + Send,
    Fut: 'static + Future<Output = String> + Send,
{
    let s = "foo".to_string();
    let r = f(s).await;
    println!("{r}");
}

async fn bar(s: String) -> String {
    println!("{s}");
    "bar".to_string()
}

#[tokio::main]
async fn main() {
    foo(bar).await;
}
foo
bar

It also works fine if s is a reference, but f is not an async function:

async fn foo<F>(f: F)
where
    F: 'static + FnOnce(&String) -> String + Send,
{
    let s = "foo".to_string();
    let r = f(&s);
    println!("{r}");
}

fn bar(s: &String) -> String {
    println!("{s}");
    "bar".to_string()
}

#[tokio::main]
async fn main() {
    foo(bar).await;
}
foo
bar

But how do I modify so f takes s by reference and is an async function? I tried but get an error, I'm sure I'm missing a lifetime annotation somewhere: (I know &String makes no sense, this is just for demonstration purposes)

use std::future::Future;

async fn foo<F, Fut>(f: F)
where
    F: 'static + FnOnce(&String) -> Fut + Send,
    Fut: 'static + Future<Output = String> + Send,
{
    let s = "foo".to_string();
    let r = f(&s).await;
    println!("{r}");
}

async fn bar(s: &String) -> String {
    println!("{s}");
    "bar".to_string()
}

#[tokio::main]
async fn main() {
    foo(bar).await;
}
error[E0308]: mismatched types
  --> src/main.rs:20:5
   |
20 |     foo(bar).await;
   |     ^^^^^^^^ one type is more general than the other
   |
   = note: expected opaque type `impl for<'a> Future<Output = String>`
              found opaque type `impl Future<Output = String>`
   = help: consider `await`ing on both `Future`s
   = note: distinct uses of `impl Trait` result in different opaque types
note: the lifetime requirement is introduced here
  --> src/main.rs:5:37
   |
5  |     F: 'static + FnOnce(&String) -> Fut + Send,
   |                                     ^^^

error: implementation of `FnOnce` is not general enough
  --> src/main.rs:20:5
   |
3  | / async fn foo<F, Fut>(f: F)
4  | | where
5  | |     F: 'static + FnOnce(&String) -> Fut + Send,
   | |                  ---------------------- doesn't satisfy where-clause
6  | |     Fut: 'static + Future<Output = String> + Send,
   | |__________________________________________________- due to a where-clause on `foo`...
...
20 |       foo(bar).await;
   |       ^^^^^^^^
   |
   = note: ...`for<'a> fn(&'a String) -> impl Future<Output = String> {bar}` must implement `FnOnce<(&String,)>`
   = note: ...but it actually implements `FnOnce<(&'0 String,)>`, for some specific lifetime `'0`

error: implementation of `FnOnce` is not general enough
  --> src/main.rs:20:14
   |
3  | / async fn foo<F, Fut>(f: F)
4  | | where
5  | |     F: 'static + FnOnce(&String) -> Fut + Send,
   | |                  ---------------------- doesn't satisfy where-clause
6  | |     Fut: 'static + Future<Output = String> + Send,
   | |__________________________________________________- due to a where-clause on `foo`...
...
20 |       foo(bar).await;
   |                ^^^^^
   |
   = note: ...`for<'a> fn(&'a String) -> impl Future<Output = String> {bar}` must implement `FnOnce<(&String,)>`
   = note: ...but it actually implements `FnOnce<(&'0 String,)>`, for some specific lifetime `'0`

Source: View source