はじめに
デバッグはエラーメッセージとの戦いです。一つひとつのエラーメッセージへの対応方法を覚えていくと、開発のペースも上がっていきます。
ここでは、初心者的に行き詰った、async/await関連のエラーメッセージを振り返ります。
なお、非同期ランタイムには tokio または actix-rt を使用しているものとします。
method not found in `impl Future` (Futureの実装にメソッドが見つからない)
間違っているコード
async関数を一つ定義します。reqwest クレートを使用して、指定したURLにHTTPリクエストを送信し、そのレスポンスを受け取ります。そのボディの内容をテキストとして出力するコード書きました。このコードはコンパイルに失敗します。
pub async fn get_html() -> Result<(), GetHtmlError> {
let response = reqwest::get("https://www.rust-lang.org");
let body = response.text().await?;
println!("{:?}", &body);
Ok(())
}
error[E0599]: no method named `text` found for opaque type `impl Future` in the current scope
--> src/lib.rs:14:25
|
14 | let body = response.text().await?;
| ^^^^ method not found in `impl Future`
Futureの実装に(textという)メソッドが見つからない、というメッセージです。コードに直接的にはFutureという単語は登場しておらず、なんのことかわかりません。また、このメッセージが指し示している行をどれだけ眺めても、構文間違いは見つかりません。詰みました。
実行できるコード
間違えているのは一つ前の行で.await?
を忘れている、という部分です。
pub async fn get_html() -> Result<(), GetHtmlError> {
// let response = reqwest::get("https://www.rust-lang.org"); <- 間違い
let response = reqwest::get("https://www.rust-lang.org").await?; // <- 正しい
let body = response.text().await?;
println!("{:?}", &body);
Ok(())
}
変数response
の値はreqwest::Response型のオブジェクトになることを想定していますが、.await?
がないため、コンパイラにFuture
型であると解釈された結果でした。
このメッセージはもしかして
method not found in impl Future
↓
.await?
を忘れている行があるよ!
...という意味かもしれません。
async functions cannot be used for tests (async関数はテストでは使用できない)
間違っているコード
先ほどのコードを実行するテストを書きました。assertが意味をなしていませんが、まずはコンパイルできるかを確認するため、cargo test
コマンドを実行してみます。そして、このコードはコンパイルに失敗します。
# [test]
async fn it_works() {
get_html().await.unwrap();
assert_eq!(true, true);
}
error: async functions cannot be used for tests
--> tests/get_html_test.rs:4:1
|
4 | async fn it_works() {
| ^----
| |
| _`async` because of this
| |
5 | | get_html().await.unwrap();
6 | | assert_eq!(true, true);
7 | | }
| |_^
async関数はテストでは使用できないと言われました。意気揚々とテストコードを書き始めましたが、1行で詰みました。
実行できるコード
// #[test] <- 間違い
# [tokio::test] // <- 正しい
async fn it_works() {
get_html().await.unwrap();
assert_eq!(true, true);
}
The Rust Programming Language 日本語版の「テストの記述法」に従って書きましたが、これだけでは情報が足りませんでした。
tokio のドキュメント内、「Attribute Macros」に説明がありました。
test
rt and macros Marks async function to be executed by runtime, suitable to test environment
test属性は、テスト環境において、ランタイムによりasync関数が実行されることを記述するためのものである
test の説明ページに、属性の記述方法が書かれています。すなわち、#[tokio::test]
と記述する、ということでした。
このメッセージはもしかして
async functions cannot be used for tests
↓
使用している非同期ランタイムのドキュメントを読もう!
...ということかもしれません。
functions in traits cannot be declared `async` (トレイト内で関数はasyncとして定義することはできない)
間違っているコード
トレイトにasync関数の定義を試みます。このコードはコンパイルに失敗します。
pub struct WebPage {}
pub trait GetHtml {
// トレイトにasync fnを定義したい
async fn get_html(&self) -> Result<(), GetHtmlError>;
}
impl GetHtml for WebPage {
async fn get_html(&self) -> Result<(), GetHtmlError> {
let response = reqwest::get("https://www.rust-lang.org").await?;
let body = response.text().await?;
println!("{:?}", &body);
Ok(())
}
}
error[E0706]: functions in traits cannot be declared `async`
--> src/lib.rs:19:5
|
19 | async fn get_html(&self) -> Result<(), GetHtmlError>;
| -----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| |
| `async` because of this
|
= note: `async` trait functions are not currently supported
= note: consider using the `async-trait` crate: https://crates.io/crates/async-trait
トレイト内で関数はasyncとして定義することはできない、というメッセージです。
注釈に外部クレートの利用を検討しろと書いてありますが、言語自体が持つ機能を利用するのに外部クレートの追加が必要なんてことはないだろうという勝手な認識で、少しコードを変更してみます。
async関数を構造体のメソッドとして定義します。そして、トレイトの関数からそのメソッドを呼び出すことにします。
impl WebPage {
// async fnは構造体自体のメソッドとして定義する
async fn async_get_html(&self) -> Result<(), GetHtmlError> {
let response = reqwest::get("https://www.rust-lang.org").await?;
let body = response.text().await?;
println!("{:?}", &body);
Ok(())
}
}
pub trait GetHtml {
// トレイトで定義するのは通常の関数とする
fn get_html(&self) -> Result<(), GetHtmlError>;
}
impl GetHtml for WebPage {
// 通常の関数からasync fnの呼び出しを試みる
fn get_html(&self) -> Result<(), GetHtmlError> {
let _ = self.async_get_html().await?;
Ok(())
}
}
error[E0728]: `await` is only allowed inside `async` functions and blocks
--> src/lib.rs:49:17
|
48 | / fn get_html(&self) -> Result<(), GetHtmlError> {
49 | | let _ = self.async_get_html().await?;
| | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ only allowed inside `async` functions and blocks
50 | | Ok(())
51 | | }
| |_____- this is not `async`
わかっていたことですが、通常の関数内で .await
は使用できません。詰みました。
実行できるコード
use async_trait::async_trait;
# [async_trait] // <- トレイトの定義に属性を記述する
pub trait GetHtml {
async fn get_html(&self) -> Result<(), GetHtmlError>;
}
# [async_trait] // <- 実装に属性を記述する
impl GetHtml for WebPage {
async fn get_html(&self) -> Result<(), GetHtmlError> {
let response = reqwest::get("https://www.rust-lang.org").await?;
let body = response.text().await?;
println!("{:?}", &body);
Ok(())
}
}
注釈に従って、async-trait
クレートを導入します。
ドキュメントの「Explanation」に記述がある通り、このクレートは小難しいコードを簡単に書けるようにするもので、機能を追加するものではありませんでした。自分の思い込みの間違いでした。
このメッセージはもしかして
functions in traits cannot be declared `async`
↓
ドキュメントを読んで正しく利用しよう!
...ということかもしれません。
`await` is only allowed inside `async` functions and blocks (awaitはasync関数またはasyncブロック内でのみ許可されている)
間違っているコード
Actix を利用します。Actixは平行アプリケーション開発のフレームワークを提供するRustのライブラリです。フレームワークの定義に従い、アクターのハンドラーを作成します。その中で、async関数の呼び出しを試みます。このコードはコンパイルに失敗します。
// Actorのハンドラーを定義する
impl Handler<Msg> for GetHtmlActor {
type Result = Result<bool, GetHtmlError>;
// ハンドラーは通常の関数で定義する
fn handle(&mut self, _msg: Msg, _ctx: &mut Context<Self>) -> Self::Result {
let _ = get_html().await?;
Ok(true)
}
}
error[E0728]: `await` is only allowed inside `async` functions and blocks
--> src/lib.rs:44:17
|
43 | / fn handle(&mut self, _msg: Msg, _ctx: &mut Context<Self>) -> Self::Result {
44 | | let _ = get_html().await?;
| | ^^^^^^^^^^^^^^^^ only allowed inside `async` functions and blocks
45 | | Ok(true)
46 | | }
| |_____- this is not `async`
現在のバージョンのActixでは、アクターのハンドラーは通常の関数として定義します。そのため、関数内で直接.await
は使用できません。初めてのActix開発、コーディング開始10分で詰みました。
実行できるコード(例)
解決する方法はいくつかあるようですが、自分がコンパイル、実行できたのは、async-std クレートを利用する方法でした。
async-std = { version = "~1.9.0", features = [ "tokio1" ] }
impl Handler<Msg> for GetHtmlActor {
type Result = Result<bool, GetHtmlError>;
// ハンドラーは通常の関数で定義する
fn handle(&mut self, _msg: Msg, _ctx: &mut Context<Self>) -> Self::Result {
// asyncブロックを定義し、その中でawaitを使用する
let get_html_task = task::spawn(async {
get_html().await?;
Ok(true)
});
task::block_on(get_html_task)
}
}
asyncブロックを定義し、その中で.await
を使用します。asyncブロック自体はasync-stdの仕組みで実行します。
このメッセージはもしかして
`await` is only allowed inside `async` functions and blocks
↓
asyncブロックを実行できる仕組みを利用しよう!
...ということかもしれません。
おわりに
どの開発言語、フレームワークでも、エラーメッセージからは直接修正方法を読み取れないことがあります。
もちろん同じメッセージでも状況によって対応方法は変わってきますが、知っているメッセージが増えてくれば、ドキュメントを読んだりネットを検索したりする時間を省略して、デバッグが効率的になります。
知っているメッセージを増やして、脱初心者を目指していきましょう。