With tokio, "future returned by 'a' is not 'Send'", with tiny example

I've read many posts about this error message but can't find a solution.

I'm using tokio for my async app server, and am stuck on the compiler error future returned by 'a' is not 'Send'. Here's a minimal example extracted from the code:

use std::io ;

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

async fn a() -> io::Result<()> {
    b().await ;
    Ok(())
}

async fn b() -> io::Result<()> {
    tokio::spawn(a()) ;
    Ok(())
}

Compiling this results in:

error: future cannot be sent between threads safely
   --> src/main.rs:14:18
    |
14  |     tokio::spawn(a()) ;
    |                  ^^^ future returned by `a` is not `Send`
    |
note: opaque type is declared here
   --> src/main.rs:13:1
    |
13  | async fn b() -> io::Result<()> {
    | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: this item depends on auto traits of the hidden type, but may also be registering the hidden type. This is not supported right now. You can try moving the opaque type and the item that actually registers a hidden type into a new submodule
   --> src/main.rs:13:10
    |
13  | async fn b() -> io::Result<()> {
    |          ^
note: future is not `Send` as it awaits another future which is not `Send`
   --> src/main.rs:9:5
    |
9   |     b().await ;
    |     ^^^ await occurs here on type `impl Future<Output = Result<(), std::io::Error>>`, which is not `Send`
note: required by a bound in `tokio::spawn`
   --> /Users/jsm/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.36.0/src/task/spawn.rs:166:21
    |
164 |     pub fn spawn<F>(future: F) -> JoinHandle<F::Output>
    |            ----- required by a bound in this function
165 |     where
166 |         F: Future + Send + 'static,
    |                     ^^^^ required by this bound in `spawn`

(There's infinite recursion in this example, but the actual code has logic preventing this.)

I understand that the .await makes the future returned by a() not Send (?), but is it possible to make that future Send somehow other than removing the .await? If not, is this limitation something that will eventually change, or is it inherent in asynchronous multithreaded Rust programs? If there's not a straightforward solution, any hints on how to rethink the problem to use a new approach?

Thanks! I'm relatively new to Rust, so all advice is most welcome.

Not sure if this is the best solution, but you can desugar the async function into a normal function that returns impl Future + Send.

fn a() -> impl Future<Output = io::Result<()>> + Send {
    async {
        b().await;
        Ok(())
    }
}

Rust Playground

1 Like

I believe the problem with the sample code is that it's recursive. When I was doing some testing on other variants, I came up with a pretty wild error that I have never seen before: Rust Playground

Full error: "cycle detected ..."
   Compiling playground v0.0.1 (/playground)
error[E0391]: cycle detected when computing type of opaque `b::{opaque#0}`
  --> src/main.rs:14:1
   |
14 | async fn b() -> io::Result<()> {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
note: ...which requires borrow-checking `b`...
  --> src/main.rs:14:1
   |
14 | async fn b() -> io::Result<()> {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: ...which requires borrow-checking `b::{closure#0}`...
  --> src/main.rs:14:32
   |
14 |   async fn b() -> io::Result<()> {
   |  ________________________________^
15 | |     tokio::spawn(async move { a().await }.boxed());
16 | |     Ok(())
17 | | }
   | |_^
   = note: ...which requires evaluating `type_op_prove_predicate` `ProvePredicate { predicate: Binder { value: TraitPredicate(<{async block@src/main.rs:15:18: 15:42} as core::marker::Send>, polarity:Positive), bound_vars: [] } }`...
   = note: ...which again requires computing type of opaque `b::{opaque#0}`, completing the cycle
note: cycle used when computing type of `b::{opaque#0}`
  --> src/main.rs:14:1
   |
14 | async fn b() -> io::Result<()> {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   = note: see https://rustc-dev-guide.rust-lang.org/overview.html#queries and https://rustc-dev-guide.rust-lang.org/query.html for more information

For more information about this error, try `rustc --explain E0391`.
error: could not compile `playground` (bin "playground") due to 1 previous error

Anyway, the !Send thing can be demonstrated without recursion, as in this example and suggested solution. The problem typically encountered is as described in this book, and not whatever weird thing is happening with the recursive calls.

If you use the suggestion by @drewtato, the compiler will be able to tell you where any !Send types are being held across await points.

1 Like

This is definitely a new thing, since recursive async functions were completely impossible until 1.77. It looks like the recursive async solver can't compute Send, which means all recursive async functions are !Send. This is probably a temporary thing, since recursive structs work fine, and I can't think of anything that would make async functions technically different from that.

3 Likes

Perfect, this is exactly what I was looking for. Thanks!

I was getting the same result even when I surrounded one of the recursive calls with if false {...}. But this is interesting-- is the rust compiler supposed to result in exactly the same output if run multiple times on the same source? Because sporadically, I saw the same error message you report sometimes. If I then merely changed an identifier and recompiled, it would revert to the "future not 'Send' error. So, it looked inconsistent to me too. Not sure what that's about.

Thanks for the link to the async book, it helps.

The compiler is not currently good with type inference [1] that is control-flow sensitive. For instance, the Tree Borrows model allows borrowing in a control-flow sensitive way (in theory) that the compiler does not currently support. Adding a condition somewhere in the function body is not going to change its mind. But annotating the function's signature (as above) will.

It is supposed to be deterministic, yes. If it isn't, that is 100% a bug.


  1. I'm including the T: Send proof in the "type inference" term, which is carrying a lot of weight in this statement. I don't actually know what the term or art is for this CFG-sensitivity otherwise. ↩︎

1 Like

That makes sense.

Thanks, that seemed weird to me too. I figured my setup was wrong or something was caching badly. If I can reliably reproduce this behavior, I'll report it as a bug.

1 Like

I agree that this seems something that ought to be fixed eventually, hopefully. I couldn't easily find an existing issue on this problem though. If there really is none, perhaps someone should open an issue using OP's example.

1 Like

I made an issue: Recursive async functions don't internally implement auto traits · Issue #123072 · rust-lang/rust · GitHub

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.