Rustにおける非同期プログラミングに関する理解
James Reed
Infrastructure Engineer · Leapcell

Rustの非同期プログラミングにおけるAsync/.await
async/.await
は、同期的なスタイルで非同期コードを記述できる、Rustの組み込み言語機能です。
async/.await
キーワードの使用方法を例を通して学びましょう。始める前に、futures
パッケージを紹介する必要があります。Cargo.toml
ファイルを編集し、次の内容を追加してください。
[dependencies] futures = "0.3"
async
を使用して非同期Futureを作成する
簡単に言うと、async
キーワードは、次のタイプの Future
を作成するために使用できます。
- 関数の定義:
async fn
- ブロックの定義:
async {}
たとえば、async
関数:
async fn hello_world() { ... }
async
キーワードは、関数のプロトタイプを変更して Future
トレイトオブジェクトを返します。次に、実行結果を新しい Future
でラップして返します。これはおおよそ次のものと同等です。
fn hello_world() -> impl Future<Output = ()> { async { ... } }
注:
async
ブロックは、匿名のFuture
トレイトオブジェクトを実装し、Generator
をカプセル化します。Generator
は、Future
を実装するジェネレーターです。Generator
は本質的にステートマシンとして機能します。async
ブロック内の操作がPoll::Pending
を返すと、ジェネレーターはyield
を呼び出して実行を放棄します。再開されると、ジェネレーターはすべてのコードが完了するまで実行を継続します。つまり、ステートマシンはComplete
状態になり、Poll::Ready
を返して、Future
の実行が完了したことを示します。
async
でマークされたコードブロックは、Future
トレイトを実装するステートマシンに変換されます。現在のスレッドをブロックする同期呼び出しとは異なり、Future
がブロッキング操作に遭遇すると、現在のスレッドの制御を放棄し、他の Future
の実行結果を待ちます。
Future
はexecutor上で実行する必要があります。たとえば、block_on
は現在のスレッドをブロックするexecutorです。
// block_on は、指定された Future の実行が完了するまで現在のスレッドをブロックします。 // このアプローチはシンプルで直接的ですが、他のランタイムexecutorはより高度な動作を提供します。 // たとえば、join を使用して複数の future を同じスレッドでスケジュールするなどです。 use futures::executor::block_on; async fn hello_world() { println!("hello, world!"); } fn main() { let future = hello_world(); // Future を返しますが、まだ出力は出力されません block_on(future); // Future を実行し、完了するのを待ちます。次に、「hello, world!」が出力されます }
await
を使用して別の非同期Futureが完了するのを待つ
上記の main
関数では、block_on
executorを使用して Future
が完了するのを待ち、コードを同期的に見せました。しかし、別の async fn
の内部で async fn
を呼び出し、後続のコードを実行する前にその完了を待つ必要がある場合はどうでしょうか? 例として:
use futures::executor::block_on; async fn hello_world() { // async関数内で別のasync関数を直接呼び出します—これは機能しますか? hello_cat(); println!("hello, world!"); } async fn hello_cat() { println!("hello, kitty!"); } fn main() { let future = hello_world(); block_on(future); }
ここでは、hello_world
async関数で、最初に別のasync関数 hello_cat
を呼び出し、次に "hello, world!"
を出力します。 出力を確認しましょう。
warning: unused implementer of `futures::Future` that must be used --> src/main.rs:6:5 | 6 | hello_cat(); | ^^^^^^^^^^^^^ = note: futures do nothing unless you `.await` or poll them ... hello, world!
予想通り、main
で block_on
を使用して Future
を実行しましたが、hello_cat
によって返された Future
は実行されませんでした。 幸いなことに、コンパイラーは、 "Futures は .await
またはポーリングしない限り何もしません。" というフレンドリーな警告を提供します。
解決策は2つあります。
.await
構文を使用します。Future
を手動でポーリングします(これはより複雑であるため、ここでは扱いません)。
.await
を使用してコードを変更してみましょう。
use futures::executor::block_on; async fn hello_world() { hello_cat().await; println!("hello, world!"); } async fn hello_cat() { println!("hello, kitty!"); } fn main() { let future = hello_world(); block_on(future); }
hello_cat()
に .await
を追加すると、出力が大幅に変わります。
hello, kitty! hello, world!
出力順序はコード順序に厳密に従うようになりました。 これは、シーケンシャルなコーディングスタイルを維持しながら、非同期実行を実現したことを意味します。 このアプローチはシンプルで効率的であり、コールバック地獄を解消します。
内部的には、すべての .await
はexecutorのように機能し、Future
状態を繰り返しポーリングします。 Pending
を返すと、yield
を呼び出します。 それ以外の場合は、ループを終了し、Future
の実行を完了します。 ロジックは大まかに次のようになります。
loop { match some_future.poll() { Pending => yield, Ready(x) => break } }
簡単に言うと、async fn
内で .await
を使用すると、別の非同期呼び出しが完了するのを待つことができます。 ただし、block_on
とは異なり、.await
は現在のスレッドをブロックしません。 代わりに、Future A
が完了するのを非同期的に待ちます。 待機中、スレッドは他の Future B
インスタンスの実行を継続できるため、並行性が実現します。
例
歌って踊るシナリオを考えてみましょう。 .await
がないと、実装は次のようになる可能性があります。
use futures::executor::block_on; struct Song { author: String, name: String, } async fn learn_song() -> Song { Song { author: "Rick Astley".to_string(), name: String::from("Never Gonna Give You Up"), } } async fn sing_song(song: Song) { println!( "Performing {}'s {} ~ {}", song.author, song.name, "Never gonna let you down" ); } async fn dance() { println!("Dancing along to the song"); } fn main() { let song = block_on(learn_song()); // 最初のブロッキングコール block_on(sing_song(song)); // 2番目のブロッキングコール block_on(dance()); // 3番目のブロッキングコール }
このコードは正しく実行されますが、3つの連続したブロッキング呼び出しが必要であり、一度に1つのタスクを完了します。 実際には、歌と踊りを同時に行うことができます。
use futures::executor::block_on; struct Song { author: String, name: String, } async fn learn_song() -> Song { Song { author: "Rick Astley".to_string(), name: String::from("Never Gonna Give You Up"), } } async fn sing_song(song: Song) { println!( "Performing {}'s {} ~ {}", song.author, song.name, "Never gonna let you down" ); } async fn dance() { println!("Dancing along to the song"); } async fn learn_and_sing() { let song = learn_song().await; sing_song(song).await; } async fn async_main() { let f1 = learn_and_sing(); let f2 = dance(); // join! マクロは複数の future を同時に実行します futures::join!(f1, f2); } fn main() { block_on(async_main()); }
ここでは、学習と歌唱には厳密な順序がありますが、どちらもダンスと共存できます。 .await
がないと、block_on(learn_song())
は現在のスレッドをブロックし、ダンスを含む他のタスクを妨げます。
したがって、.await
はRustでの非同期プログラミングに不可欠です。 これにより、複数のタスクを順番に実行するのではなく、同じスレッドで同時に実行できます。
結論
async/.await
は、同期コードのように見える非同期関数を記述するためのRustの組み込みツールです。 async
は、コードブロックを Future
トレイトを実装するステートマシンに変換します。これはexecutor上で実行する必要があります。 Future
はスレッド全体をブロックするのではなく、制御を譲り、他の Future
が実行できるようにします。
重要なポイント:
Future
は、将来値を生成するタスクを表します。async
はFuture
を作成します。.await
はFuture
をポーリングし、完了するのを待ちます。- Executor (
block_on
など) はFuture
を管理および実行します。 - Rustの
async
はゼロコストです:ヒープ割り当てや動的ディスパッチはありません。 - Rustには組み込みの非同期ランタイムは含まれていません。
tokio
、async-std
、smol
などのサードパーティライブラリがこの機能を提供します。
要約すると、async/.await
はRustで効率的で並行的なタスク実行を可能にし、コールバック地獄を解消し、非同期プログラミングを直感的にします。
Leapcellは、Rustプロジェクトをホストするための最良の選択肢です。
Leapcell は、Webホスティング、非同期タスク、Redisのための次世代サーバーレスプラットフォームです。
多言語サポート
- Node.js、Python、Go、またはRustで開発します。
無制限のプロジェクトを無料でデプロイ
- 使用量に対してのみ料金が発生します。リクエストや料金はかかりません。
比類のない費用対効果
- アイドル料金なしの従量課金制。
- 例:$25で平均応答時間60msで694万リクエストをサポートします。
合理化された開発者エクスペリエンス
- 簡単なセットアップのための直感的なUI。
- 完全に自動化されたCI/CDパイプラインとGitOps統合。
- 実用的な洞察のためのリアルタイムメトリクスとロギング。
簡単なスケーラビリティと高いパフォーマンス
- 高い同時実行性を簡単に処理するための自動スケーリング。
- 運用オーバーヘッドゼロ — 構築に集中するだけです。
ドキュメントで詳細をご覧ください!
Xでフォローしてください:@LeapcellHQ