---
title: async/await の内部メカニズム
url: https://doc.liz6.com/ja/rust/06-concurrency-and-asynchrony/02-async-await
locale: ja
area: rust
tags:
- rust
- concurrency-and-asynchrony
date: 2026-06-30
modified: 2026-07-16
description: async fn は、Future トレイトを実装するステートマシンにコンパイラによって展開されます。各 .await ポイントは1つの状態です。Pin は Future がメモリ上で移動しないこと（自己参照構造）を保証し、Waker は実行系（executor）に次のポーリングをいつ行えるかを伝えます。この展開プロセスを理解すれば、Rust 非同期処理の「ゼロコスト抽象」の仕組みと、Future がなぜ安易に drop してはいけないかが理解できます。
---

# async/await の内部メカニズム

> `async fn` は、`Future` トレイトを実装するステートマシンにコンパイラによって展開されます。各 `.await` ポイントは1つの状態です。`Pin` は `Future` がメモリ上で移動しないこと（自己参照構造）を保証し、`Waker` は実行系（executor）に次のポーリングをいつ行えるかを伝えます。この展開プロセスを理解すれば、Rust 非同期処理の「ゼロコスト抽象」の仕組みと、`Future` がなぜ安易に drop してはいけないかが理解できます。

## `async fn` はコンパイル後にどうなるか

```rust
async fn read_config() -> Config {
    let file = read_file().await;      // await point 1
    parse(&file).await                 // await point 2
}
```

コンパイラは `async fn` を `Future` トレイトを実装する匿名型（ステートマシン）に変換します：

```rust
// コンパイラによって生成される擬似コード:
enum ReadConfigFuture {
    State0 { ... },                    // 初期状態、read_file を呼び出す直前
    State1 { file_content: String },   // read_file の完了を待機中、file_content を保持
    State2 { file: String },           // parse の完了を待機中
    Done,
}

impl Future for ReadConfigFuture {
    type Output = Config;
    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Config> {
        match self {
            State0 => { /* read_file を開始、State1 に遷移 */ Poll::Pending },
            State1 { file_content } => { /* read_file が完了したか確認 */ ... },
            // ...
        }
    }
}
```

重要なのは、各 `.await` ポイントがステートマシンの遷移に対応していることです。ステートマシンは await をまたぐローカル変数を保持します。これらの変数は、`poll()` が `Pending` を返した後も、次の `poll()` 呼び出しまで存続し続ける必要があります。

## `Future` トレイト

```rust
pub trait Future {
    type Output;
    fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output>;
}
```

実行系（executor）は `poll()` をループで呼び出します。`Future` がまだ完了していない場合、`Poll::Pending` を返します。`Pending` を返す前に、`Future` は `cx.waker()` を通じて waker（コールバック関数）を登録する必要があります。この waker は、`Future` が準備可能になった場合（例：IO の完了、タイマーの期限切れ）に呼び出されます。実行系は wake を受け取ると、再度 `poll()` を実行します。

## `Pin` の役割

ステートマシンの `State1` は `file_content: String` を保持しています。もし `Future` が新しいメモリアドレスに移動（move）されると、そのアドレスを保持している内部ポインタ（存在する場合）がダングリングポインタになります。自己参照を持つ `Future`（`select!` マクロや借用されたローカル変数を含むシナリオで一般的）の場合、`Pin<&mut Self>` は `Future` が移動しないことを保証します。コンパイラは、pinned な値を移動させるために `memcpy` を生成しません。

## 単純な executor の骨格

```rust
fn block_on<F: Future>(mut future: F) -> F::Output {
    let waker = ...;                   // waker を作成
    let mut cx = Context::from_waker(&waker);
    let mut future = unsafe { Pin::new_unchecked(&mut future) };
    loop {
        match future.as_mut().poll(&mut cx) {
            Poll::Ready(v) => return v,
            Poll::Pending => std::thread::park(),  // wake を待機（本番コードでは epoll を使用）
        }
    }
}
```

これは概念的な説明に過ぎません。実際の executor（tokio, async-std など）は、ビジーウェイトではなく、IO 多重化のために epoll/kqueue/IOCP を使用します。

## 参考

- **Rust Book**: 非同期処理の章（公式ドキュメントでは現在作成中）
- **Tokio tutorial**: tokio.rs/tokio/tutorial
- **async-book**: rust-lang.github.io/async-book

*キーワード: async, await, Future, Pin, poll, state machine, executor, waker*
