Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
11章の著者のκeenです。実践Rust入門の出版後半年くらいでRustでの非同期のエコシステムに大きな変化があったのでフォローアップします。
このプルリクエストについて
主な目的は以下です。
現在のRustにはasync/await構文が入っており、扱いづらかった
Future
を自然な構文で書けるようになりました。また、使っているフレームワークもバージョンアップを重ねています。そこで本文はもう変更できませんがせめてWeb上のリソースくらいはと思ってプルリクエストを作りました。マージしてしまうと本文との対応が取れなくなるのでこのプルリクエストはopenしたままにしておくと思います。
このプルリクエストの説明以外にもコード内に多少コメントを追加しています。実践Rust入門内の順番に合わせてstart-aw → static-files → templates → log-collectorの順に読むことを想定しています。
また、このプルリクエストはκeenが一人で書いて他の共著者や編集者のレビューを受けずに投稿しているので 文責はκeenのみにあります 。
時系列
11章に関係するRust界隈の動きを簡単にまとめます。
Future
が入った。async
/await
が安定化された 🎉 。Futures 0.3.0がリリースされた。標準ライブラリの
Future
と互換。Tokio 0.2.0がリリースされた。
async
/await
対応。Async-StdというTokioの対抗馬のクレートの1.0.0がリリースされた。
async
/await
対応。async
/await
対応。async
/await
対応。async
/await
対応。Reqwestについては追記 2020/1/2: 0.10.0がリリースされました /追記async
/await
対応版の0.10.0がまだアルファステータスです。遠くないうちにReqwestでもasync
/await
が使えるようになるでしょう。非同期処理、Future、そしてasync/awaitについて軽く
コード内のコメントに解説や関連リソースへのリンクを張ったのでここでは軽い説明に留めます。
現代的なアプリケーションではIOなどの同期処理を非同期に扱いたいというモチベーションがあります。クライアントからリクエストを受けてデータベースに問い合わせて結果をクライアントに返すWebサーバを考えましょう。同期処理ではデータベースに問い合わせた結果が返ってくるまでサーバは何もせずに待ちます。他のクライアントが接続するのを待っていたとしてもです。
これでは効率が悪いので、待ち時間に別の処理をしましょうというのが非同期処理です。
こういう処理を愚直にやろうとすると、とても骨が折れます。1つの処理の間に別の処理をするということは処理を細切れにするということです。今まで1つの関数で書いていた処理が3つにも4つにも分かれ、それぞれ全然違う場所で実行されます。興味のある方は愚直なイベント駆動で書いたHello Worldを読んでみて下さい。コードを上から下に読んでも処理の流れが分からないなど、そのまま読み書きするにはいささか以上に厳しいコードになります。
そこで非同期処理を比較的まともに書くデザインパターンにFutureパターンというのがあります。Futureがあると少なくとも上から下に読めは処理の流れが分かるようになります。FutureパターンをRustで実現するための基盤が
Future
トレイトです。Futureパターンを使ったコードがどいうものかはFuture
を使ったHello Worldを読んでみて下さい。先程よりはまともになっているかと思います。実践Rust入門執筆時点ではこの仕組みだけでコードを書いていました。Future
で上から下に読めるようにはなったのですが、それでもまだ普段のHello Worldとは大きく見た目が異なります。そこでFuture
に糖衣構文を被せ、見た目を普段書く形式(直接形式)に近い形でコードを書けるようにしたのがasync
/await
です。どのくらい綺麗になったかはasync
/await
を使ったHello Worldを見ると分かりやすいです。async
/await
構文を使えば直接形式と同じ見た目でプログラムを書けるようになるのです。ということで
async
/await
は何をしているのかというと、Future
を書きやすくしています。Future
は何をしているかというと、イベント駆動な処理を書きやすくしています。async
/await
の使い方async
/await
の下地にはFuture
があると説明しました。ということでasync
とawait
はFuture
を使う挙動をします。直感的にはasync
がT -> Future<T>
、await
がFuture<T> -> T
の動きをします。async
ブロックasync
ブロックは無名のFuture
を実装した型を作ります。例えばasync { 1 }
と書くと1を返す無名のFuture
を作ります。|| { 1 }
と書くと呼ぶと1を返す無名のクロージャを作るのに似ていますね。async fn
fn () -> impl Future<Output=T> { async { ... } }
と書くのはあまりに定型文なので簡単に書けます。先程の例は以下のようにも書けます。ほとんど普通の関数と変わらない見た目ですね。await
await
はFuture<T> -> T
のような動きをします。ただしasync
の中でしか使えません。例えばasync_one
の返り値を取り出そうとして以下のように書いてもコンパイルは通りません。これは以下のようなコンパイルエラーになります。
async fn
(またはasync
ブロック) の中でしかawait
を呼べないようになっています。このコードは以下のようにasync fn
で書き換えてあげると動きます。この構造、はじめてRustを触ったときの
Result
やoption
から値が取り出せなくて悩んだ経験を思い出しませんか?だいたいFuture
もそういう類のものです。Result
やOption
はパターンマッチで剥がせますがFuture
を剥がす手段は基本的にはないです。最後までFuture
をリレーしていって下さい。Future
の中に入ったままフレームワーク(非同期ランタイム)に渡すことになります。今回のActix-WebでもAPIハンドラはasync
関数を要求されていて、中身を取り出さなくてもプログラミングできるようになっています。最終的にはActix-Webの内部で中身が取り出されます。Future
は手作りしないasyc
とawait
だけでは別にIO処理の間に他の処理をできるようになったりはしません。そういうものは非同期ライブラリに頼ります。例えばAsynt-StdのWrite::write
を使えばファイルに書き出す処理を非同期にしてくれます。あとはそれを使うときにasync
の中でawait
してあげればいいのです。ユーザは既存の
async
/await
を使って非同期ライブラリを組み合わせるだけで、Future
トレイトを実装する型を作ることはほとんどありません。非同期ランタイム
Future
は「将来実行されるタスク」であって、実行までは含んでいません。これを(多くの場合複数)動かすのはFuture
とは別の仕組みになります。Future
(= タスク)を1スレッド内で複数動かすには相応の仕組み(スケジューラ)を整える必要があります。これもユーザが手で書くことはほとんどなくて、スケジューラを提供してくれるライブラリを使います。そういうライブラリを非同期ランタイムと呼んだりします。非同期ランタイムは有名どころだとTokio、Async-Std、Actix-RT、(一応)Futuresなどがあります。ランタイムが変わるとに
Future
の作り方が違うケースもあり、「このFutureはXXXライブラリのランタイムを使ったときにしか動かない」などの動作をするFutureがたまにあります。そのため、非同期ランタイムライブラリをを中心に非同期エコシステムが広がっています。非同期ランタイムを選ぶことはただ単に実行方法を選ぶだけでなく、エコシステム全体を選ぶことになっている場合もあるので注意して下さい。大抵のライブラリはTokioで動いていますが、今回我々が使っているActix-WebはActix-RTで動いています。Actixのエコシステムに乗っかっている訳です。
注意点
特に他言語でFutureパターンを使ったことがある方向けに注意点を挙げておきます。
Futureはスレッドを使うことを含意しない
Futureを実行すると裏でスレッドが作られたりスレッドプールで処理が走るデザインもありますが、Rustの
Future
はそうではありません。処理を細切れにするのをサポートするだけです。別スレッドで実行したいなら「処理を別スレッドで実行してFuture
を返す」APIや 「Future
を受け取って別スレッドで実行する」APIを提供するライブラリを別途使う必要があります。Futureは勝手に実行されない
勝手に別スレッドで実行されないということとも繋がるのですが、Futureを作っただけでは実行されません。例えば以下のようなコードを書いたとしましょう。
この
async_hello()
が返すFuture
は実行されません。「main
がすぐに終了してしまうから100msくらい待たないといけない」などの理由ではなく、何秒待っても実行されません。Future
はpoll
メソッド を呼ばないと実行されない仕組みになっているのです。「じゃあ
poll
を呼べばいいじゃん」と思うかもしれませんが、poll
を手で呼ぶのは少し面倒な作りになっています。興味のある方は私が以前書いたブログ記事 を読んでみて下さい。ということでほとんどの場合でFuture
を最終的に実行するにはpoll
を呼ぶ準備を整えてくれるライブラリ(=非同期ランタイム)を使うことになります。