rustlifetimerust-tokiorust-async-std

Why is the Send trait required inside my match block?


I think my question relates to Rust Issue 57017.

The following code does not compile and produces error: future cannot be sent between threads safely due to future created by async block is not 'Send' originating in async fn execute_fail. On the other hand async fn execute_work compiles and has the same functions and logic as execute_fail but in a less elegant manner.

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

async fn process() {}

async fn execute_fail() -> Result<(), Box<dyn std::error::Error>> {
    match stream().await {
        Err(err) => {
            return Err(err);
        }
        Ok(()) => {
            process().await;
        }
    }
    Ok(())
}

async fn execute_work() -> Result<(), Box<dyn std::error::Error>> {
    let mut is_success = false;
    match stream().await {
        Err(err) => {
            return Err(err);
        }
        Ok(()) => {
            is_success = true;
        }
    }

    if is_success {
        process().await;
    }
    Ok(())
}

async fn my_fn() {
    tokio::spawn(async {
        if let Err(err) = execute_fail().await {
            panic!("error here");
        }
    });

    tokio::spawn(async {
        if let Err(err) = execute_work().await {
            panic!("error here");
        }
    });
}

Gives:

error: future cannot be sent between threads safely
   --> src/lib.rs:39:5
    |
39  |     tokio::spawn(async {
    |     ^^^^^^^^^^^^ future created by async block is not `Send`
    | 
   ::: /playground/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-1.11.0/src/task/spawn.rs:127:21
    |
127 |         T: Future + Send + 'static,
    |                     ---- required by this bound in `tokio::spawn`
    |
    = help: the trait `Send` is not implemented for `dyn std::error::Error`
note: future is not `Send` as this value is used across an await
   --> src/lib.rs:15:13
    |
10  |     match stream().await {
    |           -------------- has type `Result<(), Box<dyn std::error::Error>>` which is not `Send`
...
15  |             process().await;
    |             ^^^^^^^^^^^^^^^ await occurs here, with `stream().await` maybe used later
16  |         }
17  |     }
    |     - `stream().await` is later dropped here

I would like an explanation why execute_fail does not compile where as execute_work does. The compiler note note: future is not Send as this value is used across an await doesn't make sense to me as I don't see how any values are being used across an await.


Solution

  • I would like an explanation why execute_fail does not compile where as execute_work does.

    When you match stream().await it essentially works as:

    let v = stream().await;
    match v {
       ...
    }
    

    So v is alive for the entirety of the match, meaning as far as the compiler is concerned its "life" overlaps with the process().await call. Values must be Send to live through an await call, as the coroutine may be resumed on a different thread than it was suspended.

    Of note: the code here can be significantly simplified:

    async fn execute_fail() -> Result<(), Box<dyn std::error::Error>> {
        if let Err(err) = stream().await {
            return Err(err);
        }
        process().await;
        Ok(())
    }
    

    or even

    async fn execute_fail() -> Result<(), Box<dyn std::error::Error>> {
        stream().await?;
        process().await;
        Ok(())
    }