Search code examples
asynchronousrustrust-tokio

How to deal with non-Send futures in a Tokio spawn context?


Tokio's spawn can only work with a Send future. This makes the following code invalid:

async fn async_foo(v: i32) {}

async fn async_computation() -> Result<i32, Box<dyn std::error::Error>> {
    Ok(1)
}

async fn async_start() {
    match async_computation().await {
        Ok(v) => async_foo(v).await,
        _ => unimplemented!(),
    };
}

#[tokio::main]
async fn main() {
    tokio::spawn(async move {
        async_start().await;
    });
}


The error is:

future cannot be sent between threads safely
the trait `Send` is not implemented for `dyn std::error::Error`

If I understand correctly: because async_foo(v).await might yield, Rust internally have to save all the context which might be on a different thread - hence the result of async_computation().await must be Send - which dyn std::error::Error is not.

This could be mitigated if the non-Send type can be dropped before the .await, such as:

async fn async_start() {
    let result;
    match async_computation().await {
        Ok(v) => result = v,
        _ => return,
    };

    async_foo(result).await;
}

However once another .await is needed before the non-Send type is dropped, the workarounds are more and more awkward.

What a is a good practice for this - especially when the non-Send type is for generic error handling (the Box<dyn std::error::Error>)? Is there a better type for errors that common IO ops implements (async or not) in Rust? Or there is a better way to group nested async calls?


Solution

  • Most errors are Send so you can just change the return type to:

    Box<dyn std::error::Error + Send>
    

    It's also common to have + Sync.