Send 模拟

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

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


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

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

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

use std::rc::Rc;
#[derive(Default)]
struct NotSend(Rc<()>);
async fn bar() {}
async fn foo() {
    NotSend::default();
    bar().await;
}

fn require_send(_: impl Send) {}

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

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

use std::rc::Rc;
#[derive(Default)]
struct NotSend(Rc<()>);
async fn bar() {}
async fn foo() {
    let x = NotSend::default();
    bar().await;
}
fn require_send(_: impl Send) {}
fn main() {
   require_send(foo());
}
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 点。

use std::rc::Rc;
#[derive(Default)]
struct NotSend(Rc<()>);
async fn bar() {}
async fn foo() {
    {
        let x = NotSend::default();
    }
    bar().await;
}
fn require_send(_: impl Send) {}
fn main() {
   require_send(foo());
}