Send の近似
一部の async fn ステートマシンはスレッド間で送っても安全ですが、
そうでないものもあります。async fn の Future が Send であるかどうかは、
非 Send 型が .await ポイントをまたいで保持されるかどうかによって決まります。
コンパイラは、値が .await ポイントをまたいで保持される可能性があるタイミングを
できる限り近似しようとしますが、現在、この解析はいくつかの箇所で保守的すぎます。
たとえば、単純な非 Send 型、おそらく Rc を含む型を考えてみましょう。
#![allow(unused)]
fn main() {
use std::rc::Rc;
#[derive(Default)]
struct NotSend(Rc<()>);
}
NotSend 型の変数は、async fn が返す結果の Future 型が Send でなければならない場合でも、
async fn 内の一時値として短時間現れることができます。
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 を変数に格納すると、それは .await の後までドロップされず、
その時点で async fn は別のスレッド上で実行されている可能性があります。
Rc は Send ではないため、スレッドをまたいで移動することを許可すると健全ではありません。
これに対する単純な解決策の 1 つは、.await の前に Rc を drop することですが、
残念ながら現在はそれでは機能しません。
この問題をうまく回避するには、非 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());
}