TL;DR
Rustの安定版コンパイラでは、トレイト定義の中に直接async fnを書くことができません。最も手軽な解決策は、async-traitクレートを追加し、トレイトとすべてのimplブロックの両方に#[async_trait]を付けることです。
# Cargo.toml
[dependencies]
async-trait = "0.1"
tokio = { version = "1", features = ["full"] }
use async_trait::async_trait;
#[async_trait]
trait Fetcher {
async fn fetch(&self, url: &str) -> String;
}
struct HttpFetcher;
#[async_trait]
impl Fetcher for HttpFetcher {
async fn fetch(&self, url: &str) -> String {
format!("fetched: {}", url)
}
}
このエラーが発生する原因
トレイト内にasyncメソッドを書くと、コンパイラが処理を止めてしまいます:
trait Fetcher {
async fn fetch(&self, url: &str) -> String; // error[E0706]
}
エラーの全文:
error[E0706]: functions in traits cannot be declared async
--> src/main.rs:2:5
|
2 | async fn fetch(&self, url: &str) -> String;
| -----^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
| |
| `async` because of this
|
= note: consider using the `async-trait` crate: https://crates.io/crates/async-trait
= note: see issue #91611 <https://github.com/rust-lang/rust/issues/91611> for more information
なぜこうなるのかを説明します。async fnを書くと、Rustは裏側で戻り値の型として匿名のimpl Futureを生成します。トレイトでは、この匿名型が実装ごとに異なります。安定版Rustにはこれをトレイトのシグネチャで表現する手段がありません。これはdyn Traitのオブジェクト安全性ルールに深く関係しています。この機能(RPITIT — Return Position Impl Trait In Traits)は安定版への導入までに何年もかかり、Rust 1.75でようやくリリースされました。
修正方法1: async-traitクレートを使う(ほぼすべてのケースで有効)
Axum、Tower、そして非同期Rustエコシステムの大部分がこのアプローチを採用しています。実績のある方法です。
ステップ1: 依存関係を追加する:
cargo add async-trait
ステップ2: インポートし、トレイトとすべてのimplブロックの両方にアノテーションを付ける:
use async_trait::async_trait;
#[async_trait]
trait Database {
async fn query(&self, sql: &str) -> Vec<String>;
async fn execute(&self, sql: &str) -> bool;
}
struct Postgres;
#[async_trait]
impl Database for Postgres {
async fn query(&self, sql: &str) -> Vec<String> {
// 実際の実装をここに書く
vec![sql.to_string()]
}
async fn execute(&self, sql: &str) -> bool {
true
}
}
内部の仕組みとして、async-traitはasyncメソッドをPin<Box<dyn Future + Send>>を返すように書き換えます。このヒープアロケーションがdyn Traitと組み合わせて動作できる理由です。わずかな実行時コストがありますが、ほとんどのアプリケーションでは無視できる程度です。
dyn Traitでも動作する
use async_trait::async_trait;
#[async_trait]
trait Notifier {
async fn notify(&self, message: &str);
}
struct EmailNotifier;
#[async_trait]
impl Notifier for EmailNotifier {
async fn notify(&self, message: &str) {
println!("Email: {}", message);
}
}
async fn send_notification(notifier: &dyn Notifier, msg: &str) {
notifier.notify(msg).await;
}
修正方法2: impl Futureを手動で返す(追加依存なし)
追加の依存関係を避けたい場合やdyn Traitが不要な場合は、明示的なimpl Futureを返す方法があります。これはRPITITの安定化の一環として、Rust 1.75で安定版に導入されました。
use std::future::Future;
trait Fetcher {
fn fetch(&self, url: String) -> impl Future<Output = String> + Send;
}
struct HttpFetcher;
impl Fetcher for HttpFetcher {
fn fetch(&self, url: String) -> impl Future<Output = String> + Send {
async move { format!("fetched: {}", url) }
}
}
重要な制限が一つあります:このトレイトをdyn Fetcherとして使用することはできません。ジェネリクスによる静的ディスパッチでのみ機能します。実行時のポリモーフィズムが必要な場合は、修正方法1に戻ってください。
修正方法3: Nightly — トレイト内のネイティブ async fn
Nightly版Rustでは、フィーチャーフラグを使うことで、クレートなしに直接asyncトレイトメソッドを書くことができます:
#![feature(async_fn_in_trait)]
trait Fetcher {
async fn fetch(&self, url: &str) -> String;
}
本番コードではこの方法は避けてください。Nightlyのフィーチャーはコンパイラのバージョン間で予告なく壊れることがあります。安定版では修正方法1または修正方法2を使用してください。
どの修正方法を使うべきか?
dyn Trait(実行時ディスパッチ)が必要? →async-traitクレート(修正方法1)- 静的ディスパッチのみ、Rust 1.75以上? → トレイト内の
impl Future(修正方法2)— 追加依存なし - Nightlyで試したい? → フィーチャーフラグを使ったネイティブ
async fn in trait(修正方法3)
修正の確認
クリーンビルドを実行する:
cargo build
# 期待される結果: エラーなし、コンパイル成功
コード生成をスキップした素早いチェック:
cargo check
Tokioで非同期テストを実行する:
cargo test
よくある間違い: implブロックへの#[async_trait]付け忘れ
async-traitを追加した後、トレイト定義にだけアノテーションを付けてimplブロックを忘れるのは、最もよくある二次エラーです:
// 間違い — implブロックにアトリビュートがない
#[async_trait]
trait Worker {
async fn run(&self);
}
struct MyWorker;
impl Worker for MyWorker { // エラー!
async fn run(&self) {}
}
すべてのimplブロックに — トレイトだけでなく — #[async_trait]が必要です。一つでも忘れると、コンパイラが教えてくれます。
参考リンク
- crates.io の async-trait クレート
- Rust issue #91611 — トレイト内async fnの安定化トラッキング
- Rust 1.75 リリースノート — RPITITの安定化

