gue*_*ter 3 thread-safety rust trait-objects rust-tokio
这个简单的程序会产生编译器错误:
#[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(())
}
Run Code Online (Sandbox Code Playgroud)
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
Run Code Online (Sandbox Code Playgroud)
我真的不明白这个错误是什么意思。当我删除函数的返回类型时bar,它可以工作,但这里的实际错误是什么?
Box<dyn Error>是一种不透明类型。它可以包含任何类型。
假设它包含一种类型,该类型无法安全地发送到与创建它的线程不同的线程。例如, a MutexGuard,当从获取互斥锁的同一线程中删除该互斥锁时,必须释放该互斥锁。或者Rc,非原子地减少 drop 上的引用计数,因此如果移动到另一个线程,可能会导致 drop 上的数据争用。那么我们不应该将它发送到另一个线程。我们说类型没有实现Send。因为Box<dyn Error>可能包含这样的类型,所以它本身并没有实现Send。
然而,tokio::spawn()未来可能会在.await点与线之间移动。这是为了提高效率:tokio使用工作窃取调度程序,这意味着它将任务转移到不太繁忙的线程(以及 CPU 核心)。但是假设Box<dyn Error>将包含一个未实现的类型Send,例如MutexGuard,它将在线程 A 中的调用之前创建tokio::join!(),并在调用之后(可能在线程 B 中)删除!(因为tokio::join()有一个隐含的.await)。这意味着这是不安全的,因此未来foo()也不安全Send,并且您不能将其派生到任务中。
修复方法很简单:确保Box<dyn Error>是Send. + Send这可以通过添加一个绑定来完成:
async fn bar() -> Result<(), Box<dyn std::error::Error + Send>> {
println!("Hello world");
Ok(())
}
Run Code Online (Sandbox Code Playgroud)