注記: このガイドは、長い間あまり作業されていなかった後、現在書き直しが進められています。作業中であり、未整備の部分が多く、既存の内容もやや粗い状態です。
はじめに
この本は、Rust における非同期プログラミングのガイドです。最初の一歩を踏み出し、より高度なトピックについてさらに知るための手助けとなるように設計されています。非同期プログラミングの経験(Rust でも他の言語でも)は前提としていませんが、すでに Rust には慣れていることを前提としています。Rust について学びたい場合は、The Rust Programming Language から始めるとよいでしょう。
この本には主に 2 つの部があります。第 1 部は初心者向けのガイドで、順番に読むことを想定しており、まったくの初心者から中級レベルまで進めるように設計されています。第 2 部は、より高度なトピックに関する独立した章の集まりです。第 1 部を読み終えた後、またはすでに async Rust の経験がある場合に役立つはずです。
この本は複数の方法で読み進めることができます。
- 最初から最後まで順番に読むことができます。これは、async Rust を初めて学ぶ人に推奨される進め方であり、少なくとも本の第 1 部についてはそうです。
- Web ページの左側に概要目次があります。
- 広いトピックについて情報が欲しい場合は、トピック索引から始めるとよいでしょう。
- 特定のトピックに関するすべての議論を探したい場合は、詳細索引から始めるとよいでしょう。
- あなたの疑問が FAQ で答えられているかどうか確認できます。
非同期プログラミングとは何か、なぜそれを行うのか?
並行プログラミングでは、プログラムは同時に複数のことを行います(または、少なくともそう見えます)。スレッドを使ったプログラミングは、並行プログラミングの一形態です。スレッド内のコードは逐次的なスタイルで書かれ、オペレーティングシステムがスレッドを並行に実行します。非同期プログラミングでは、並行性は完全にプログラム内で発生します(オペレーティングシステムは関与しません)。非同期ランタイム(Rust では単なる別のクレートです)が、プログラマーが await キーワードを使って明示的に制御を譲ることと連携して、非同期タスクを管理します。
オペレーティングシステムが関与しないため、非同期の世界におけるコンテキストスイッチは非常に高速です。さらに、非同期タスクはオペレーティングシステムのスレッドよりもメモリのオーバーヘッドがはるかに小さくなります。このため、非常に多くの並行タスクを処理する必要があり、それらのタスクが多くの時間を待機(たとえば、クライアントからの応答や IO の待機)に費やすシステムには、非同期プログラミングが適しています。また、メモリ量が非常に限られており、スレッドを提供するオペレーティングシステムを持たないマイクロコントローラーにも、非同期プログラミングは適しています。
非同期プログラミングは、タスクをどのように実行するか(並列性と並行性のレベル、制御フロー、スケジューリングなど)について、プログラマーに細かな制御も提供します。つまり、多くの用途において、非同期プログラミングは表現力が高く、かつ使いやすいものになり得ます。特に、Rust における非同期プログラミングには、キャンセルという強力な概念があり、多くの異なる形態の並行性(spawn とそのバリエーション、join、select、for_each_concurrent などを含む構文を使って表現される)をサポートしています。これらにより、タイムアウト、一時停止、スロットリングといった概念の、合成可能で再利用可能な実装が可能になります。
Hello, world!
async Rust がどのようなものかを少し感じてもらうために、ここに「hello, world」の例を示します。並行性はなく、非同期であることを実際に活かしているわけではありません。非同期関数を定義して使用し、“hello, world!” を出力します。
// Define an async function.
async fn say_hello() {
println!("hello, world!");
}
#[tokio::main] // Boilerplate which lets us write `async fn main`, we'll explain it later.
async fn main() {
// Call an async function and await its result.
say_hello().await;
}
詳しいことは後で説明します。今のところは、async fn を使って非同期関数を定義し、.await を使ってそれを呼び出している点に注目してください。Rust の非同期関数は、await されない限り何もしません1。
この本のすべての例と同様に、完全な例(たとえば Cargo.toml を含む)を確認したい場合や、自分のローカル環境で実行したい場合は、この本の GitHub リポジトリで見つけることができます。例: examples/hello-world。
Async Rust の開発
Rust の async 機能はしばらく前から開発されていますが、言語の「完成した」部分ではありません。Async Rust(少なくとも stable コンパイラーと標準ライブラリで利用できる部分)は信頼性が高く、高性能です。最大規模のテック企業における最も要求の厳しい状況の一部で、本番環境で使用されています。しかし、未整備の部分や粗い部分(信頼性ではなく使いやすさの意味で粗い部分)もあります。async Rust を学ぶ過程で、こうした部分のいくつかに出くわす可能性があります。未整備の部分のほとんどには回避策があり、それらはこの本で扱います。
現在、非同期イテレーター(ストリームとしても知られています)を扱う部分で、多くのユーザーが粗い部分を見つけています。トレイトにおける async の一部の使い方は、まだ十分にはサポートされていません。非同期破棄については、よい解決策がありません。
Async Rust は現在も活発に取り組まれています。開発の動向を追いたい場合は、Async Working Group のホームページを確認できます。そこにはロードマップも含まれています。または、Rust Project 内の async プロジェクト目標を読むこともできます。
Rust はオープンソースプロジェクトです。async Rust の開発に貢献したい場合は、メインの Rust リポジトリにあるコントリビューション用ドキュメントから始めてください。
-
これは実際には悪い例です。なぜなら、
printlnはブロッキング IO であり、一般に非同期関数内でブロッキング IO を行うのは良くない考えだからです。ブロッキング IO が何であるかはchapter TODOで、非同期関数内でブロッキング IO を行うべきではない理由はchapter TODOで説明します。 ↩