Rust エラー E0728 の修正: 非同期コンテキスト以外での '.await' の使用

beginner🦀 Rust2026-07-14| Rust 1.39+ (Stable), あらゆるOS (Linux, macOS, Windows)

Error Message

error[E0728]: 'await' is only allowed inside 'async' functions and blocks
#rust#async#tokio#backend

問題点おそらくサービスを構築中で、reqwest でJSONペイロードを取得したり、sqlx でデータベースにクエリを投げたりしている最中でしょう。関数呼び出しに .await を付ければ動くはずだと思いきや、コンパイラが次のようなエラーを吐き出します。

error[E0728]: 'await' is only allowed inside 'async' functions and blocks
  --> src/main.rs:10:5
   |
9  | fn main() {
   |    ---- これはおそらく 'async' ではありません
10 |     let response = my_async_function().await;
   |                    ^^^^^^^^^^^^^^^^^^^^^^^^^ 'async' 関数やブロックの中でのみ許可されます

根本的な原因Rust の非同期モデルは独特です。タスクが自動的にバックグラウンドで実行される Node.js や Python とは異なり、Rust の Future は「遅延(lazy)」評価です。何かが実際にポーリング(poll)するまで、それらはアイドル状態のままで何もしません。

.await を呼び出すと、関数を実行の中断や再開が可能なステートマシンに変換するようコンパイラに指示することになります。この状態遷移の魔法は、そのために設計されたコンテキスト内でのみ機能します。標準的な同期関数は中断する方法を知りません。同期関数内で .await を使おうとすると、コンパイラは行き止まりに突き当たり、E0728 をスローします。

エラーの修正方法### 1. 関数に async を付与する最も手っ取り早い修正方法が、通常は正解です。関数定義の前に async を付けるだけです。これにより、その関数が値をすぐに返すのではなく、最終的に結果を生成する Future を返すことを Rust に伝えます。

修正前:

fn fetch_data() {
    let data = some_async_call().await; // エラー E0728
}

修正後:

async fn fetch_data() {
    let data = some_async_call().await; // 正常にコンパイルされます
}

2. main() 用の非同期ランタイムを初期化する新規の開発者は、fn main() でこの壁に突き当たることがよくあります。デフォルトでは Rust のエントリーポイントは同期型であり、await できません。これを解決するには、タスクを管理するためのエグゼキュータが必要です。Tokio は業界標準であり、非同期 Rust プロジェクトの 80% 以上で使用されています。

まず、Cargo.toml に 1.x バージョンの tokio を追加します。

[dependencies]
tokio = { version = "1", features = ["full"] }

次に、#[tokio::main] マクロを使用して main 関数を変換します:

#[tokio::main]
async fn main() {
    let result = my_async_task().await;
    println!("タスク完了: {:?}", result);
}

3. block_on による非同期と同期のブリッジasync が選択肢にない状況に遭遇することもあるでしょう。たとえば、std::fmt::Display のような同期トレイトを実装している場合や、レガシーなコードベースと統合する場合などです。こうした「同期専用」のゾーンでは、block_on を使用して現在のスレッドを Future の完了まで強制的に待機させます。

futures::executor::block_on を使用する場合:

use futures::executor::block_on;

fn sync_function() {
    // Futureが解決されるまでスレッドをブロックします
    let data = block_on(my_async_task());
    println!("データを取得: {:?}", data);
}

4. async ブロックの使用大規模な同期スコープ内で、ごく一部の非同期ロジックだけが必要な場合は、async ブロックを使用します。ただし、ブロック自体が Future であることを忘れないでください。ランタイムに渡されるまで実行されません。

fn main() {
    let my_future = async {
        do_something().await;
    };
    
    // 上記のコードはまだ実行されていません。実行する必要があります:
    futures::executor::block_on(my_future);
}

検証cargo check を実行して修正を確認します。E0728 が消えれば、構文は問題ありません。ただし、以下のランタイムの落とし穴に注意してください。

  • ランタイムのネスト: 既存の非同期関数内で block_on を呼び出さないでください。Tokio のようなランタイムでは、即座にパニック(panic)が発生します。- デッドロック: 非同期タスクが確実に終了するようにしてください。そうしないと、block_on がスレッド全体を永久にハングさせてしまいます。## 予防のヒント- 最初から非同期で始める: アプリがネットワークやディスクにアクセスする場合は、最初から #[tokio::main] を使用しましょう。「非同期の浸食(Async creep)」は現実に起こります。後から同期コードベースを非同期化するのは非常に困難です。- 非同期を上位に伝播させる: コールスタック全体を async に保つことを目指してください。block_on は、最上位レベルや、非同期化が不可能な厳密な境界でのみ使用するようにします。- トレイトのサポートを確認する: Iterator のような標準トレイトは、まだ非同期をサポートしていません。必要な場合は、async-trait クレートや futures クレートの Stream を使用してください。

Related Error Notes