問題点おそらくサービスを構築中で、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を使用してください。

