なぜasyncの中でformat!を使うとエラーが出るのか

2019年11月にリリースされたRust 1.39では，非同期プログラミングを支援するために`async/await`が言語機能として導入されました．
機能自体の解説は[「Rustの非同期プログラミングをマスターする」](https://tech-blog.optim.co.jp/entry/2019/11/08/163000)などを読んでいただくと良いでしょう．
本稿では`async/await`の基本的な知識は前提としたうえで`format!`との組み合わせにより生じる不可解なエラーについて解説します．

# エラー例
次のコードは`String`を受け取る`async fn`に`format!`で作成した文字列を渡し，`await`しています．

```rust
async fn take_string(_: String) {}

fn main() {
    tokio::spawn(async {
        take_string(format!("")).await;
    });
}
```

このコードをコンパイルすると

```terminal
$ cargo c
    Checking crate_name v0.1.0 (/path/to/crate)
error[E0277]: `*mut (dyn std::ops::Fn() + 'static)` cannot be shared between threads safely
   --> src/main.rs:4:5
    |
4   |     tokio::spawn(async {
    |     ^^^^^^^^^^^^ `*mut (dyn std::ops::Fn() + 'static)` cannot be shared between threads safely
    |
   ::: /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/tokio-0.2.0-alpha.6/src/executor.rs:100:40
    |
100 |     F: Future<Output = ()> + 'static + Send,
    |                                        ---- required by this bound in `tokio::executor::spawn`
    |
    = help: within `core::fmt::Void`, the trait `std::marker::Sync` is not implemented for `*mut (dyn std::ops::Fn() + 'static)`
    = note: required because it appears within the type `std::marker::PhantomData<*mut (dyn std::ops::Fn() + 'static)>`
    = note: required because it appears within the type `core::fmt::Void`
    = note: required because of the requirements on the impl of `std::marker::Send` for `&core::fmt::Void`
    = note: required because it appears within the type `std::fmt::ArgumentV1<'_>`
    = note: required because it appears within the type `[std::fmt::ArgumentV1<'_>; 0]`
    = note: required because it appears within the type `for<'r, 's, 't0, 't1, 't2, 't3, 't4, 't5, 't6, 't7, 't8> {[&'r str; 0], &'s [&'t0 str], &'t1 [&'t2 str; 0], (), [std::fmt::ArgumentV1<'t3>; 0], &'t4 [std::fmt::ArgumentV1<'t5>], &'t6 [std::fmt::ArgumentV1<'t7>; 0], std::fmt::Arguments<'t8>, std::string::String, impl std::future::Future, impl std::future::Future, ()}`
    = note: required because it appears within the type `[static generator@src/main.rs:4:24: 6:6 for<'r, 's, 't0, 't1, 't2, 't3, 't4, 't5, 't6, 't7, 't8> {[&'r str; 0], &'s [&'t0 str], &'t1 [&'t2 str; 0], (), [std::fmt::ArgumentV1<'t3>; 0], &'t4 [std::fmt::ArgumentV1<'t5>], &'t6 [std::fmt::ArgumentV1<'t7>; 0], std::fmt::Arguments<'t8>, std::string::String, impl std::future::Future, impl std::future::Future, ()}]`
    = note: required because it appears within the type `std::future::GenFuture<[static generator@src/main.rs:4:24: 6:6 for<'r, 's, 't0, 't1, 't2, 't3, 't4, 't5, 't6, 't7, 't8> {[&'r str; 0], &'s [&'t0 str], &'t1 [&'t2 str; 0], (), [std::fmt::ArgumentV1<'t3>; 0], &'t4 [std::fmt::ArgumentV1<'t5>], &'t6 [std::fmt::ArgumentV1<'t7>; 0], std::fmt::Arguments<'t8>, std::string::String, impl std::future::Future, impl std::future::Future, ()}]>`
    = note: required because it appears within the type `impl std::future::Future`
```

と書いたコードよりも長いエラーが出てしまいました！

# 解決策
`format!`の結果を変数に代入します．

```rust
async fn take_string(_: String) {}

fn main() {
    tokio::spawn(async {
        let s = format!("");
        take_string(s).await;
    });
}
```

するとコンパイルが通るようになります[^0]．
具体的には，**`.await`式中に一時変数として`format!`が存在しているとエラー**になってしまいます．

[^0]: エラーの内容でググってきた人はここでブラウザバックして大丈夫です

# 最小の再現例
上のエラーは本質的には以下のコードによって再現されます．

```rust
async fn take_string(_: String) {}

// 引数がSendであることをチェック
fn is_send<T: Send>(_: T) {}

fn main() {
    // format!を使うFutureを作成
    let fut = async {
        take_string(format!("")).await;
    };
    is_send(fut); // エラー: futがSendではない
}
```

関数`is_send`は引数に`Send`を要求するのですが，`format!`と`.await`とを組み合わせたせいで`async`ブロックが`Send`を満たさなくなってしまいます（このことを`!Send`であるともいいます）．
`tokio::spawn`なども同様に引数の`Future`が`Send`であることを要求するため，例で挙げたような`async`関数を渡すとエラーになってしまうのです．

# `Send`とは何か
それではなぜ非同期ランタイムは`Future`が`Send`であることを要求するのでしょうか．また，`Send`とはそもそも何なのでしょうか．

[Rustはデータ競合からの安全性をコンパイル時にチェックしています](https://doc.rust-jp.rs/book/second-edition/ch16-00-concurrency.html)．
例えば，`Rc`は参照カウント方式のスマートポインタですが，参照カウントの操作でスレッド間同期を行っていないので，複数スレッドから同時に同じメモリを指す`Rc`を操作できてしまうと二重`free`やデータ競合などの**未定義動作**を引き起こす可能性が生じてしまいます．
これをコンパイル時に防ぐためにRustは「スレッド間を跨いで移動できるデータ型」を表すトレイト`Send`（と`Sync`）を導入しました．`thread::spawn`などのスレッド間でデータを移動する可能性がある関数は引数型に`Send`を要求し，`Rc`のようなスレッドを跨ぐと危険な型は`Send`を実装しないので，上で挙げたような危険な呼び出しを防ぐことができます．
また，`Send`（や`Sync`）はauto traitと呼ばれており，基本的にはコンパイラによって自動的に実装されます．このとき，**ある型が`Send`であるためにはその型のフィールドはすべて`Send`でなくてはいけません（ここ後で重要）**．ですから，`Rc`を含む構造体は自動的に`!Send`（`Send`を満たさない）となります．

さて，非同期ランタイムが`Send`を要求する理由に戻りましょう．一般的な非同期ランタイムはスレッドプールやワークスティーリングを用いて空いているスレッドにタスク（`Future`）を割り当てることでスループットを上げる戦略をとっています．
つまり，別のスレッドに`Future`に移動させる可能性があるので，`Future`が`Send`を実装している必要があるのです．

ちなみに，スレッドプールを用いるようなランタイムでも`Send`でない`Future`を実行するためのAPIを備えている場合があります．
行う処理の都合上`Send`ではなくなってしまう場合は，このようなトレイト制約に`Send`を持たないAPIを探しましょう．

# なぜ`format!`を使った`Future`が`Send`を実装しないのか
## `Future`が`Send`である条件
ここまででは非同期ランタイムの関数が`Future`に`Send`を要求することがあることおよびその理由を学びました．
それでは，なぜ`format!`と`await`の組み合わせて作った`Future`（上のコードの`async`ブロック）は`Send`を実装しなくなってしまうのでしょうか．
それにはRustにおける`async`がどのように働くのかを理解する必要があります．
**Rustでは（JavaScriptやC#等での`async/await`とは異なり）`async`関数を呼び出しても処理の実行が始まりません**（同様に，`async`ブロックを書いても処理は動きません）．
処理が実際に実行され始めるのは`async`関数の戻り値の`Future`を`await`してからとなります．
この`async`関数の戻り値の`Future`には「現在どこまで処理が進んだか」と「次の処理を進めるために必要なデータ」が格納されており，`await`時にはこれらのデータを参照して処理を行います．
さて，「現在どこまで処理が進んだか」を表すにはどの`await`で処理を中断しているのかを表す整数のフラグがあれば十分です．では，「次の処理を進めるために必要なデータ」は何になるのでしょうか．
これは**`await`を跨いで存在するローカル変数**となります．次の`async`関数を考えてみましょう．

```rust
async fn foo() {
    let x = 10; // ここでxに代入した
    some_future().await; // ここで処理を中断する可能性がある
    println!("{}", x); // awaitを跨いでxを使用している
}
```

`async`関数`foo`が返す`Future`は`some_future().await;`のところで処理を中断する可能性があります．処理を中断するということはいったん関数のスコープから抜けてしまうということですから，関数のローカル変数`x`を破棄しなければいけません．しかし，ここで破棄してしまうと処理を再開した際に`x`の値が失われてしまい，その後の`println!("{}", x);`が未定義動作となってしまいます．
Rustではこれを防ぐために，`await`の時点でその`await`を跨いで使われているローカル変数の値を`Future`に保存します．`foo`の場合は`foo`が返す`Future`に`x`の値を保管しておき，`await`から復帰したときには保存しておいた値を用いるので正しく`x`の値を表示することができます．

さあ，前の節で述べた「型が`Send`であるには，すべてのフィールドが`Send`でなくてはいけない」という事実を思い出しましょう．
`foo`の例では`x`はただの整数型だったので，当然`Send`です．では，`x`が`Rc`のような`!Send`な型の場合はどうなるでしょうか？このとき，`foo`が返す`Future`には`x`の値を保存するためのフィールドが含まれますから，`foo`の戻り値も`x`の`!Send`を引き継いでしまいます．つまり，**`async`によって生成される`Future`を`Send`にするには`await`を跨ぐローカル変数をすべて`Send`としなければいけません**．

## `format!`は`!Send`なテンポラリを生成する
話の核心にぐっと近づいてきました．残る疑問は「なぜ`format!`を使うと`!Send`なローカル変数が`await`を跨いでしまうのか」です．
これには`format!`マクロの展開形式が大きく関わっています．次の`format!`式を見てみましょう．

```rust
format!("foo{}bar{}baz", 0, "hehe")
```

これを，`cargo-expand`を使ってマクロ展開すると

```rust
format(Arguments::new_v1(&["foo", "bar", "baz"], &[&0, &"hehe"]))
```

となります（細部は省略しました）．このコードで一番重要なのは

```rust
Arguments::new_v1(&["foo", "bar", "baz"], &[&0, &"hehe"])
```

の部分です．ここでは[Arguments](https://doc.rust-lang.org/std/fmt/struct.Arguments.html)という構造体を生成しています．
`Arguments`はフォーマット文字列と引数を格納する構造体です．ユーザコードからは`format_args!`によって生成可能で，上に登場した[format](https://doc.rust-lang.org/std/fmt/fn.format.html)関数で文字列化したり[write](https://doc.rust-lang.org/std/fmt/fn.write.html)関数によって`Write`に出力したりすることができます．
`Arguments::new_v1`はコンパイラによってのみ利用可能な`Arguments`のコンストラクタです．その引数を見てみると`&["foo", "bar", "baz"]`とフォーマット引数を使わない部分の文字列と`&[&0, &"hehe"]`という引数部分を受け取っており，確かにフォーマットに必要なデータを保持していることを確認することができます．

さて，上のコードで`&[&0, &"hehe"]`と書いたのにお気づきでしょうか．`0`と`"hehe"`の型は違うのにも関わらず，ここではまとめて一つの配列を構築してしまっています．本当のマクロ展開では`unsafe`を使って**フォーマット引数の型を消去した配列**を構築しています[^2]．ここの**引数の型を消去している**というのが問題のキモです．どのような型の引数を`format!`に渡しても，構築される`Arguments`の型は同じになります．**たとえ引数が`!Send`であっても**です．
これが意味することは，**`Arguments`型は`Send`を実装しない**ということです．なぜならば，`Arguments`は内部に`Send`でない型を持つ可能性があり，そのことは型消去によって隠蔽されてしまいます．すなわち，並列安全性を保証するには最も保守的な判断，すなわち`Arguments`は`Send`でないという判断を下さなければいけないのです．これは実際に起きた問題であり，[昔の`Arguments`の実装では`Send`を満たしてしまっていたためにsafeなRustプログラムでデータ競合を引き起こすことができてしまいました](https://github.com/rust-lang/rust/issues/45197)．

[^2]: ここの型消去はRust 1.0以前に`format!`（の前身）が[初めて導入されたとき](https://github.com/rust-lang/rust/commit/ffb670ffcd69ed8e7cd13a7f06375ede752349e2)から行われていました．おそらく`Display::fmt`や`Debug::fmt`の呼び出しをインライン化することによるコードブロートを嫌ったのだと推測していますが，正確な理由は分かりませんでした（識者求む）

`Arguments`のこの`!Send`性は実際には`[&0, &"hehe"]`の型消去した配列が`!Send`であることを引き継いでいます．
そしてこの配列はこの文のみで使われるテンポラリな値ですが，Rustにおいて**テンポラリは文の末尾まで生存します**．
つまり

```rust
take_string(format!("")).await;
```

と記述したとき，

```rust
take_string(format(Arguments::new_v1(&[""], &[])))
    .await;                                //^ここでフォーマット引数の配列が生成され
        //^ここで破棄される
```

とコンパイラによって解釈されます．つまり，**フォーマット引数の配列が`await`を跨いで使用される**と判定され，生成される`Future`に含まれてしまいます．結果，その`Future`自体も配列の`!Send`を引き継いで`!Send`となってしまいます．

この仕組みを考えるとなぜ`format!`の結果を`let`で受けるとエラーが消えるのかも分かります．

```rust
let s = format!("");
take_string(s).await;
```

とコードを書き直したとき，これは`format!`マクロを展開すると

```rust
let s = format(Arguments::new_v1(&[""], &[])) ;
take_string(s).await;                  //^生成 ^破棄
```

となり，フォーマット引数の配列は`let`文の末尾で破棄されます．つまり，この配列は`await`が来る前に寿命を迎えるので`Future`の`Send`性には影響を及ぼさないのです．

# 今後の展望
この記事で扱った`format!`と`async/await`の組み合せの問題は言語設計のコーナーケースを踏んでしまったとも言えます．
`format!`単体で見た際には`Arguments`が`!Send`であることは安全性の保証のために必須ですし，それが問題を及ぼすことはありませんでした．
しかし，`async/await`と組み合せたことによってこの詳細な実装の都合が現れてきてしまい，その結果として極めて分かりづらいエラーに繋がってしまいました．
Rustコミュニティもこの問題を[認識](https://github.com/rust-lang/rust/issues/64960)しており，Rust 1.41で解決される見込みです．この記事では最後にどのような解決方法が提案されたかを紹介します．

## 1. `format!`の展開を変える
`let`の導入によって`Future`を`Send`にする方法を応用すると，次のように書くこともできます．

```rust
receive_string({
    let s = format!(""); // ←この行で!Sendな配列が破棄されるのでawaitを跨がない
    s // sがブロックの結果の値となり，関数の引数として渡される
}).await;
```

これを一歩進めて，`format!`の展開自体を

```rust
{
    let s = format(Arguments::new_v1(...));
    s
}
```

としてあげると冒頭のコード例でも問題なくコンパイルが通ります．この変更はテクニカルには破壊的（振る舞いを変えるプログラムが存在する）のですが，問題ないと判断され[既にマージされています](https://github.com/rust-lang/rust/pull/64856)．
ですので，**Rust 1.41（もしくはNightly）からはもはや`format!`の問題は存在しません**．

## 2. 配列のライフタイムを短くする
今回の問題の原因は`[&0, &"hehe"]`といったフォーマット引数の配列が`await`を跨いで生存すると扱われていたことでした．
ですが，`await`から復帰したあとのこの配列への操作は`drop`だけですし，この配列は参照の配列なのでその`drop`も特に何もしません．
つまり，Rustコンパイラが不必要に長く配列のライフタイムを推論した結果エラーになったと考えることもできます．
ですので，[コンパイラがより精密にライフタイムを扱うことで解決できるかもしれません](https://github.com/rust-lang/rust/issues/64960#issuecomment-539989214)．