導入
ネットワーク越しにデータを取りに行く、ファイルを読み込む——こうした処理は「結果が返ってくるまで時間がかかる」ものです。同期処理でこれを書くと、待っている間プログラム全体が止まってしまいます。非同期処理(async/await) は、「待っている間に他の作業を進める」ための仕組みです。
説明
Rust では、async fn で定義した関数は、呼び出してもすぐには実行されません。代わりに Future(「まだ完了していない計算」を表す値)を返します。
async fn fetch_data() -> String {
// 実際にはここでネットワーク越しの通信などを行う(時間がかかる)
String::from("data")
}
async fn run() {
println!("開始");
let data = fetch_data().await; // 完了まで待つ(その間、他の非同期タスクは進められる)
println!("受信: {data}");
}
async fn fetch_data() -> String… 見た目は普通の関数ですが、戻り値の型は実際にはFuture<Output = String>です。呼び出しただけでは中身は実行されず、「まだ実行されていない計算」がまず手に入ります。.await… そのFutureの完了を待ちます。待っている間、実行環境(ランタイム)は他のFutureの作業を進めることができます——これが同期処理との決定的な違いです。- ただし
Futureは「回してくれる誰か」がいないと動きません。それが ランタイム(代表的なものがtokioクレート)です。
#[tokio::main]
async fn main() {
run().await;
}
#[tokio::main]…tokioランタイムを起動し、async fn mainを実行できるようにする目印です(fn main自体は非同期関数にできないため、こうしたマクロで橋渡しします)。
同期処理と非同期処理の違いをイメージで比べてみましょう。
sequenceDiagram
participant M as メイン処理
participant A as 処理A(待ち時間あり)
participant B as 処理B
Note over M,B: 同期(直列)
M->>A: 開始
A-->>M: 完了まで何もできず待つ
M->>B: Aが終わってからようやく開始
Note over M,B: 非同期(async/await)
M->>A: 開始(.await)
M->>B: 待ち時間の間にBを進める
B-->>M: 完了
A-->>M: 完了
非同期処理が力を発揮するのは、I/O待ちが多い処理(Webサーバーが同時に多くのリクエストを捌く、複数のネットワーク先へ同時に問い合わせる、など)です。逆に、CPUを使い切るような計算処理では async/await の恩恵は薄く、素直に同期コードやスレッド(第10章)を使う方が適しています。「待つ」ことが多いなら async/await、というのが選び方の目安です。
まとめ
async fnは呼んだだけでは実行されず、Future(未完了の計算)を返す。.awaitは完了を待つが、その間に他のFutureを進められる(協調的なマルチタスク)。Futureを実際に動かすにはtokioなどのランタイムが必要。- ネットワークやファイルなど、待ち時間の多い処理で真価を発揮する。