RustのAsync/Await入門ガイド

[![表紙](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/1ee87959-09a4-47f7-898a-0398116f3433.png)](https://leapcell.io/?lc_t=q_rustasyncawait)

## Rust の非同期プログラミングにおける async/.await

`async/.await` は Rust に組み込まれた言語機能であり、同期コードのように記述しながら非同期処理を行うことができます。

それでは、`async/.await` の使い方を例を通じて学びましょう。始める前に、`futures` クレートを追加する必要があります。`Cargo.toml` ファイルを編集し、以下の内容を追加してください。

```toml
[dependencies]
futures = "0.3"
```

## async を使って非同期 Future を作成する

簡単に言えば、`async` キーワードは次のような Future を作成するために使用できます。

- 関数を定義する： `async fn`
- ブロックを定義する： `async {}`

例えば、`async` 関数：

```rust
async fn hello_world() {
    ...
}
```

`async` キーワードを付けることで、この関数は `Future` trait を実装するオブジェクトを返すようになります。つまり、次のように書き換えられます。

```rust
fn hello_world() -> impl Future<Output = ()> {
    async { ... }
}
```

> **注意**：`async` ブロックは匿名の `Future` trait を実装し、`Generator` をラップします。`Generator` は状態機械（state machine）であり、`.await` を使用することで、非同期コードが `Poll::Pending` を返した際に `yield` を呼び出し、実行権を手放します。一度実行が再開されると、`resume` によって残りの処理が進行し、最終的にすべてのコードが完了すると `Poll::Ready` を返し、Future の実行が終了します。

`async` でマークされたコードブロックは、`Future` を実装した状態機械に変換されます。同期処理が現在のスレッドをブロックするのとは異なり、`Future` は実行中にブロッキング状態になった場合、スレッドの制御権を他の `Future` に譲ります。

`Future` を実行するには、実行器（executor）が必要です。例えば `block_on` はスレッドをブロックして `Future` の実行を完了させることができます。

```rust
// 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` を使って `Future` の完了を待つため、一見同期コードのように見えます。しかし、`async fn` の中で他の `async fn` を呼び出し、その完了を待ってから次の処理を実行したい場合はどうすればよいでしょうか？

例えば、次のコードを見てみましょう。

```rust
use futures::executor::block_on;

async fn hello_world() {
    // 非同期関数の中で別の非同期関数を呼び出す。問題はないだろうか？
    hello_cat();
    println!("hello, world!");
}

async fn hello_cat() {
    println!("hello, kitty!");
}

fn main() {
    let future = hello_world();
    block_on(future);
}
```

このコードでは、`hello_world` 関数内で `hello_cat` を呼び出し、その後に `"hello, world!"` を出力しています。実行結果を見てみましょう。

```shell
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` 関数内の `Future` は `block_on` を通じて実行されましたが、`hello_cat` の `Future` は実行されていません。しかし、コンパイラは親切に次の警告を出しています：

> "futures do nothing unless you `.await` or poll them"

つまり、`.await` を使うか、手動で `Future` をポーリング（poll）する必要があります。後者は少し複雑なので、まず `.await` を使ってみましょう。

```rust
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` を付けた結果、出力が以下のように変わります。

```shell
hello, kitty!
hello, world!
```

出力の順番がコードの記述順と一致しています。このように `.await` を使うことで、非同期処理を自然な順序で記述することができ、コールバック地獄（callback hell）を回避できます。

実際には、`.await` 自体が実行器のように動作し、内部的には `Future` の状態をポーリング（poll）しています。擬似コードで表すと次のようになります。

```rust
loop {
    match some_future.poll() {
        Pending => yield,
        Ready(x) => break
    }
}
```

要するに、`async fn` の中で `.await` を使うことで、他の非同期呼び出しが完了するのを待つことができます。ただし、`block_on` とは異なり、`.await` はスレッドをブロックせず、`Future A` の完了を待つ間に `Future B` を実行することができます。この仕組みにより並行処理が実現されます。

## 例：歌って踊る

`await` を使わない場合、以下のようなコードが考えられます。

```rust
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!(
        "{} の {} を歌います ~ {}",
        song.author, song.name, "Never gonna let you down"
    );
}

async fn dance() {
    println!("音楽に合わせて踊り始めました！");
}

fn main() {
    let song = block_on(learn_song()); // 1回目のブロック
    block_on(sing_song(song)); // 2回目のブロック
    block_on(dance()); // 3回目のブロック
}
```

このコードは正しく動作しますが、各タスクを順番にブロックしながら実行しているため、並列実行ができません。しかし、歌いながら踊ることは可能なはずです。

次のコードでは、`.await` を活用して並行処理を行います。

```rust
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!(
        "{} の {} を歌います ~ {}",
        song.author, song.name, "Never gonna let you down"
    );
}

async fn dance() {
    println!("音楽に合わせて踊り始めました！");
}

async fn learn_and_sing() {
    // .await を使用して曲を覚えるのを待つが、スレッドはブロックされない
    let song = learn_song().await;

    // 歌うには曲を覚えてからでないといけないので、sing_song は learn_song の完了を待つ
    sing_song(song).await;
}

async fn async_main() {
    let f1 = learn_and_sing(); // 曲を覚えて歌う Future
    let f2 = dance(); // 踊る Future

    // join! マクロを使い、複数の Future を並列で処理する
    // learn_and_sing Future がブロックされたら、dance Future が実行される
    // dance もブロックされたら、learn_and_sing に制御が戻る
    // 両方ともブロックされたら、async_main はブロック状態になり、main の block_on に制御が渡る
    futures::join!(f1, f2);
}

fn main() {
    block_on(async_main());
}
```

このコードでは、**曲を覚えて歌う処理** と **踊る処理** を並行して実行しています。`await` を使わずに `block_on(learn_song())` としてしまうと、曲を覚える間スレッドが完全にブロックされ、他の処理（踊るなど）ができなくなります。

したがって、`.await` は非同期プログラミングにとって非常に重要であり、同じスレッド内で複数のタスクを並列に実行することを可能にします。

## Rust の async/.await の仕組み

ここまでで `async/.await` の基本的な使い方は理解できたと思います。次に、その背後でどのように動作しているのかを簡単に説明します。

Rust の `async/.await` は、**状態機械（state machine）** を用いてコードの実行フローを管理します。これは `Executor`（実行器）と組み合わせて使用され、協調的なタスクスイッチング（coroutine）を実現します。

`async` を使うと、Rust コンパイラはその関数を `Future` trait を実装する構造体に変換します。開発者は手動で `Future` や `poll` メソッドを書く必要はなく、コンパイラが自動的に適切な非同期処理の状態機械を作成してくれます。

## まとめ

`async/.await` は Rust に組み込まれた非同期プログラミングのためのツールであり、**見た目は同期コードのように記述しつつ、内部的には非同期で実行できる** というメリットがあります。

- **`Future`** ：非同期タスクを表し、将来的に値を取得できる。Rust では怠惰評価（lazy evaluation）されるため、ポーリング（poll）されるまで実行されない。
- **`async`** ：非同期関数や非同期ブロックを作成し、`Future` を返す。
- **`.await`** ：`Future` の完了を待つ。内部的には `Future` の状態をポーリングし、`Pending` なら `yield` し、`Ready` なら処理を続行する。
- **`executor`** ：`Future` の管理・実行を担当する。`block_on` はスレッドをブロックして `Future` を実行するシンプルな実行器。
- **Rust の `async/.await` はゼロコストである**：ヒープメモリの割り当ても不要で、動的ディスパッチなしで実装される。
- **Rust の `async/.await` には実行時（runtime）が必要**：標準ライブラリには非同期実行のためのランタイムが含まれていないため、`tokio`、`async-std`、`smol` などのサードパーティ製の実行環境が必要になる。

結論として、**`async/.await` は Rust の非同期プログラミングモデルであり、並行タスクの作成と実行のための手段です。`async` を使って `Future` を生成し、`.await` で実行とスケジューリングを制御することで、シンプルかつ強力な非同期処理が実現できます。**

---

### [私たちはLeapcell、Rustプロジェクトのホスティングの最適解です。](https://leapcell.io/?lc_t=q_rustasyncawait)

[![Leapcell](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/b5362fc1-6d20-4683-b741-7b976ee4fedf.png)](https://leapcell.io/?lc_t=q_rustasyncawait)

[Leapcell](https://leapcell.io/?lc_t=q_rustasyncawait)は、Webホスティング、非同期タスク、Redis向けの次世代サーバーレスプラットフォームです：

**複数言語サポート**

- Node.js、Python、Go、Rustで開発できます。

**無制限のプロジェクトデプロイ**

- 使用量に応じて料金を支払い、リクエストがなければ料金は発生しません。

**比類のないコスト効率**

- 使用量に応じた支払い、アイドル時間は課金されません。
- 例: $25で6.94Mリクエスト、平均応答時間60ms。

**洗練された開発者体験**

- 直感的なUIで簡単に設定できます。
- 完全自動化されたCI/CDパイプラインとGitOps統合。
- 実行可能なインサイトのためのリアルタイムのメトリクスとログ。

**簡単なスケーラビリティと高パフォーマンス**

- 高い同時実行性を容易に処理するためのオートスケーリング。
- ゼロ運用オーバーヘッド — 構築に集中できます。

[ドキュメントで詳細を確認！](https://docs.leapcell.io/)

[![Try Leapcell](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3967621/b5d37ae8-4c59-440d-a65a-35fc6deb2c60.png)](https://leapcell.io/?lc_t=q_rustasyncawait)

Xでフォローする：[@LeapcellHQ](https://x.com/LeapcellHQ)

---

[ブログでこの記事を読む](https://leapcell.io/blog/understanding-async-await-in-rust)