Send模拟

一些async fn状态机是可以安全地跨线程传递(Send)的,但另外的不可以。一个async fnFuture是否Send取决于是否有非Send类型跨越.await点被持有了。当编译器发现 有些值可能会跨.await持有时。编译器尽可能地模拟Send,但是这种分析今天在一些地方过于 保守。

例如,考虑一个简单的非Send类型,可能是一种持有Rc的类型:


# #![allow(unused_variables)]
#fn main() {
use std::rc::Rc;

#[derive(Default)]
struct NotSend(Rc<()>);
#}

类型NotSend的变量可能会很简单地作为临时变量出现在async fn函数中,甚至会出现在 async fn函数返回的Future类型必须是Send的时候:

async fn bar() {}
async fn foo() {
    NotSend::default();
    bar().await;
}

fn require_send(_: impl Send) {}

fn main() {
    require_send(foo());
}

然而,如果我们改动foo来存一个NotSend变量,这个例子就不再编译了:


# #![allow(unused_variables)]
#fn main() {
async fn foo() {
    let x = NotSend::default();
    bar().await;
}
#}
error[E0277]: `std::rc::Rc<()>` cannot be sent between threads safely
  --> src/main.rs:15:5
   |
15 |     require_send(foo());
   |     ^^^^^^^^^^^^ `std::rc::Rc<()>` cannot be sent between threads safely
   |
   = help: within `impl std::future::Future`, the trait `std::marker::Send` is not implemented for `std::rc::Rc<()>`
   = note: required because it appears within the type `NotSend`
   = note: required because it appears within the type `{NotSend, impl std::future::Future, ()}`
   = note: required because it appears within the type `[static generator@src/main.rs:7:16: 10:2 {NotSend, impl std::future::Future, ()}]`
   = note: required because it appears within the type `std::future::GenFuture<[static generator@src/main.rs:7:16: 10:2 {NotSend, impl std::future::Future, ()}]>`
   = note: required because it appears within the type `impl std::future::Future`
   = note: required because it appears within the type `impl std::future::Future`
note: required by `require_send`
  --> src/main.rs:12:1
   |
12 | fn require_send(_: impl Send) {}
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

error: aborting due to previous error

For more information about this error, try `rustc --explain E0277`.

这个错误是正确的。如果我们把x存到变量中去,它不会被丢弃(drop),直到.await之后, 这时async fn可能在另外一个线程中运行。因为Rc不是Send的,允许它穿过线程是不合理的。 一个简单的解决方法是应该在.await之前drop掉这个Rc,但是不幸的是现在这种方法还不能 工作。

为了规避这个问题,你可能需要引入一个块作用域来封装任何非Send变量。这会让编译器更容易 发现这些变量不会存活超过.await点。


# #![allow(unused_variables)]
#fn main() {
async fn foo() {
    {
        let x = NotSend::default();
    }
    bar().await;
}
#}