How to constrain the returned future for `AsyncFn*` with a trait bound?

Consider this example:

fn callback<F>(f: F)
where
    F: AsyncFn() + Send + 'static,
{
    tokio::spawn(async move {
        f().await;
    });
}

#[tokio::main]
async fn main() {
    callback(async || {});
}

The compiler reports an error

error: future cannot be sent between threads safely
   --> src/main.rs:5:5
    |
5   | /     tokio::spawn(async move {
6   | |         f().await;
7   | |     });
    | |______^ future created by async block is not `Send`
    |
    = help: within `{async block@src/main.rs:5:18: 5:28}`, the trait `Send` is not implemented for `<F as AsyncFnMut<()>>::CallRefFuture<'_>`
note: future is not `Send` as it awaits another future which is not `Send`
   --> src/main.rs:6:9
    |
6   |         f().await;
    |         ^^^ await occurs here on type `<F as AsyncFnMut<()>>::CallRefFuture<'_>`, which is not `Send`
note: required by a bound in `tokio::spawn`
   --> /playground/.cargo/registry/src/index.crates.io-1949cf8c6b5b557f/tokio-1.43.0/src/task/spawn.rs:168:21
    |
166 |     pub fn spawn<F>(future: F) -> JoinHandle<F::Output>
    |            ----- required by a bound in this function
167 |     where
168 |         F: Future + Send + 'static,
    |                     ^^^^ required by this bound in `spawn`
help: consider further restricting the associated type
    |
3   |     F: AsyncFn() + Send + 'static, <F as AsyncFnMut<()>>::CallRefFuture<'_>: Send
    |                                  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

However, <F as AsyncFnMut<()>>::CallRefFuture<'_>: Send is not writeable. So, how do we constrain the returned future of the async closure?

1 Like

Use the normal Fn() syntax.

fn callback<F, Fut>(f: F)
where
    F: FnOnce() -> Fut,
    F: Send + 'static,
    Fut: Future + Send,
{
    tokio::spawn(async move {
        f().await;
    });
}

#[tokio::main]
async fn main() {
    callback(async || {});
}
1 Like

This means we cannot use async closure in this scenario, right? If the async closure captures the enviroment, it won't implement Fn* trait. However, the appearance of the async closure is to resolve the capture issues, which is instead restricted in the scenario.

Yes, that feature does not exist yet.