Send 估计

有的async fn状态机在线程间传递时是安全的,有的不是。一个async fnfuture是否具有Sendtrait是根据 是否有 跨越带.await的语句的 非Send类型来判断的。编译器会尽可能估计这些值可能跨越.await的时点,但是在今天看来,这种分析显得太过保守了。 举个例子,考虑一个简单的非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存储在一个变量中,这个例子就不能编译了:

However, if we change foo to store NotSend in a variable, this example no longer compiles:

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存储到变量中,直到.await结束后它才应被释放,到那时async fn可能在另一个线程上运行了。既然Rc不是Send,允许其在线程间传递就是不安全的。一个简单的解决方案就是在.await前就把Rcdrop掉,不过这个目前不行。

为了解决这个问题,可以通过一个块作用域来把非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());
}