This simple program yields a compiler error:
#[tokio::main]
async fn main() {
tokio::spawn(async {
foo().await;
});
}
async fn foo() {
let f1 = bar();
let f2 = bar();
tokio::join!(f1, f2);
}
async fn bar() -> Result<(), Box<dyn std::error::Error>> {
println!("Hello world");
Ok(())
}
error[E0277]: `(dyn std::error::Error + 'static)` cannot be sent between threads safely
--> src/main.rs:5:18
|
5 | tokio::spawn(async {
| _____------------_^
| | |
| | required by a bound introduced by this call
6 | | foo().await;
7 | | });
| |_____^ `(dyn std::error::Error + 'static)` cannot be sent between threads safely
|
= help: the trait `Send` is not implemented for `(dyn std::error::Error + 'static)`
= note: required for `Unique<(dyn std::error::Error + 'static)>` to implement `Send`
= note: required because it appears within the type `Box<dyn Error>`
= note: required because it appears within the type `Result<(), Box<dyn Error>>`
= note: required because it appears within the type `MaybeDone<impl Future<Output = Result<(), Box<dyn Error>>>>`
= note: required because it appears within the type `(MaybeDone<impl Future<Output = Result<(), Box<dyn Error>>>>, MaybeDone<impl Future<Output = Result<(), Box<dyn Error>>>>)`
= note: required because it captures the following types: `ResumeTy`, `impl Future<Output = Result<(), Box<dyn Error>>>`, `(MaybeDone<impl Future<Output = Result<(), Box<dyn Error>>>>, MaybeDone<impl Future<Output = Result<(), Box<dyn Error>>>>)`, `&mut (MaybeDone<impl Future<Output = Result<(), Box<dyn Error>>>>, MaybeDone<impl Future<Output = Result<(), Box<dyn Error>>>>)`, `u32`, `[closure@join.rs:95:17]`, `PollFn<[closure@join.rs:95:17]>`, `()`
note: required because it's used within this `async fn` body
I don't really understand what the error means. When I remove the return type of the bar
function it works, but what is the actual error here?
Box<dyn Error>
is an opaque type. It may contain any type.
Suppose it contains a type that cannot be safely send to a different thread than the one it was created in. For example, a MutexGuard
, that must release the mutex when dropped from the same thread as it acquired it. Or an Rc
, that decrements the reference count on drop non-atomically, and thus can cause a data race on drop if moved to another thread. Then we should not send it to another thread. We say the type does not implement Send
. Because Box<dyn Error>
may contain such types, it itself does not implement Send
.
However, tokio::spawn()
may move the future between threads between .await
points. This is to improve efficiency: tokio
uses a work-stealing scheduler, meaning it will move tasks to threads (and therefore CPU cores) that are less busy. But suppose the Box<dyn Error>
would contain a type that does not implement Send
, for example MutexGuard
, it would be created before the tokio::join!()
call, in thread A, and dropped after it, potentially in thread B! (because tokio::join()
has an implicit .await
). This mean this is not safe, therefore the future of foo()
is not Send
either, and you cannot spawn it into a task.
The fix is simple: ensure the Box<dyn Error>
is Send
. This can be done by adding a + Send
bound to it:
async fn bar() -> Result<(), Box<dyn std::error::Error + Send>> {
println!("Hello world");
Ok(())
}