9
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

そのエラーメッセージはもしかして async/await関連

Last updated at Posted at 2021-02-25
1 / 21

はじめに

デバッグはエラーメッセージとの戦いです。一つひとつのエラーメッセージへの対応方法を覚えていくと、開発のペースも上がっていきます。

ここでは、初心者的に行き詰った、async/await関連のエラーメッセージを振り返ります。

なお、非同期ランタイムには tokio または actix-rt を使用しているものとします。


method not found in `impl Future` (Futureの実装にメソッドが見つからない)


間違っているコード

async関数を一つ定義します。reqwest クレートを使用して、指定したURLにHTTPリクエストを送信し、そのレスポンスを受け取ります。そのボディの内容をテキストとして出力するコード書きました。このコードはコンパイルに失敗します。

lib.rs
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?を忘れている、という部分です。

lib.rs
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コマンドを実行してみます。そして、このコードはコンパイルに失敗します。

tests/get_html_test.rs
# [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行で詰みました。


実行できるコード

get_html_test.rs
// #[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関数の定義を試みます。このコードはコンパイルに失敗します。

lib.rs
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関数を構造体のメソッドとして定義します。そして、トレイトの関数からそのメソッドを呼び出すことにします。

lib.rs
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は使用できません。詰みました。


実行できるコード

Cargo.toml
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関数の呼び出しを試みます。このコードはコンパイルに失敗します。

lib.rs
// 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 クレートを利用する方法でした。

Cargo.toml
async-std = { version = "~1.9.0", features = [ "tokio1" ] }
lib.rs
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ブロックを実行できる仕組みを利用しよう!

...ということかもしれません。


おわりに

どの開発言語、フレームワークでも、エラーメッセージからは直接修正方法を読み取れないことがあります。

もちろん同じメッセージでも状況によって対応方法は変わってきますが、知っているメッセージが増えてくれば、ドキュメントを読んだりネットを検索したりする時間を省略して、デバッグが効率的になります。

知っているメッセージを増やして、脱初心者を目指していきましょう。

9
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?