本文へスキップ
BecomeCoder

Rustコース · 第12章 プロジェクトとツール · レッスン48

async/await の考え方 ― Future と .await

ローカル実施

導入

ネットワーク越しにデータを取りに行く、ファイルを読み込む——こうした処理は「結果が返ってくるまで時間がかかる」ものです。同期処理でこれを書くと、待っている間プログラム全体が止まってしまいます。非同期処理(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 などのランタイムが必要。
  • ネットワークやファイルなど、待ち時間の多い処理で真価を発揮する。