rustrust-tokio

Using `AsyncFnOnce` with `tokio::spawn`


When using AsyncFnOnce with tokio::spawn, I get the following error:

fn spawn_asyncfn<F>(f: F) -> tokio::task::JoinHandle<()>
where
    F: 'static + AsyncFnOnce() -> (),
{
    let task = f();
    tokio::spawn(task)
}
error[E0277]: `<F as AsyncFnOnce<()>>::CallOnceFuture` cannot be sent between threads safely
   --> src\lib.rs:6:18
    |
6   |     tokio::spawn(task)
    |     ------------ ^^^^ `<F as AsyncFnOnce<()>>::CallOnceFuture` cannot be sent between threads safely
    |     |
    |     required by a bound introduced by this call
    |
    = help: the trait `Send` is not implemented for `<F as AsyncFnOnce<()>>::CallOnceFuture`
note: required by a bound in `tokio::spawn`
   --> C:\Users\Martin\.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: 'static + AsyncFnOnce() -> (), <F as AsyncFnOnce<()>>::CallOnceFuture: Send
    |                                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

However, when I follow the compiler suggestion, it gives me the following error:

fn spawn_asyncfn<F>(f: F) -> tokio::task::JoinHandle<()>
where
    F: 'static + AsyncFnOnce() -> (),
    <F as AsyncFnOnce<()>>::CallOnceFuture: Send,
{
    let task = f();
    tokio::spawn(task)
}
error[E0658]: use of unstable library feature `async_fn_traits`
 --> src\lib.rs:4:5
  |
4 |     <F as AsyncFnOnce<()>>::CallOnceFuture: Send,
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error[E0658]: the precise format of `Fn`-family traits' type parameters is subject to change
 --> src\lib.rs:4:5
  |
4 |     <F as AsyncFnOnce<()>>::CallOnceFuture: Send,
  |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: use parenthetical notation instead: `AsyncFnOnce() -> ()`
  |
  = note: see issue #29625 <https://github.com/rust-lang/rust/issues/29625> for more information

Does that mean it is currently impossible to tokio::spawn an AsyncFnOnce, until async_fn_traits is stabilized? Or am I missing something?


Solution

  • Currently it is not possible. Quoting Async Closures MVP:

    You can't directly name the output future

    When you name an async callable bound with the old style, before first-class async fn trait bounds, then as a side-effect of needing to use two type parameters, you can put additional bounds (e.g. + Send or + 'static) on the Future part of the bound, like:

    fn async_callback<F, Fut>(callback: F)
    where
        F: FnOnce() -> Fut,
        Fut: Future<Output = String> + Send + 'static
    { todo!() }
    

    There isn't currently a way to put similar bounds on the future returned by calling an async closure, so if you need to constrain your callback futures like this, then you won't be able to use async closures just yet.

    We expect to support this in the medium/long term via a return-type-notation syntax.

    Following that last link, the planned syntax would look something like:

    fn spawn_asyncfn<F>(f: F) -> tokio::task::JoinHandle<()>
    where
        F: 'static + AsyncFnOnce() -> (),
        F(..): Send, // <--------
    {
        let task = f();
        tokio::spawn(task)
    }
    

    Unfortunately, I wasn't able to get it working on nightly on an AsyncFnOnce. The original RFC doesn't seem to describe support for the function traits.