Asyncトレイト

トレイト内の async メソッドは、1.75 リリースで安定化されました。これには、トレイト内で戻り値位置の impl Trait を使うためのサポートが必要でした。async fn の脱糖では -> impl Future<Output = ...> が含まれるためです。

しかし、ネイティブサポートがあっても、async fn にはいくつか特有の落とし穴があります。

  • 戻り値位置の impl Trait は、スコープ内のすべてのライフタイムをキャプチャします(そのため、特定の借用パターンは表現できません)。

  • async トレイトは trait objects と一緒には使えません(dyn Trait はサポートされません)。

async_trait クレートは、いくつかの注意点はあるものの、マクロを通じて dyn サポートの回避策を提供します。

// Copyright 2024 Google LLC
// SPDX-License-Identifier: Apache-2.0

use async_trait::async_trait;
use std::time::Instant;
use tokio::time::{Duration, sleep};

#[async_trait]
trait Sleeper {
    async fn sleep(&self);
}

struct FixedSleeper {
    sleep_ms: u64,
}

#[async_trait]
impl Sleeper for FixedSleeper {
    async fn sleep(&self) {
        sleep(Duration::from_millis(self.sleep_ms)).await;
    }
}

async fn run_all_sleepers_multiple_times(
    sleepers: Vec<Box<dyn Sleeper>>,
    n_times: usize,
) {
    for _ in 0..n_times {
        println!("Running all sleepers...");
        for sleeper in &sleepers {
            let start = Instant::now();
            sleeper.sleep().await;
            println!("Slept for {} ms", start.elapsed().as_millis());
        }
    }
}

#[tokio::main]
async fn main() {
    let sleepers: Vec<Box<dyn Sleeper>> = vec![
        Box::new(FixedSleeper { sleep_ms: 50 }),
        Box::new(FixedSleeper { sleep_ms: 100 }),
    ];
    run_all_sleepers_multiple_times(sleepers, 5).await;
}
  • async_trait は簡単に使えますが、これを実現するためにヒープ割り当てを使っている点に注意してください。このヒープ割り当てには性能上のオーバーヘッドがあります。

  • async trait の言語サポートに関する課題は、この授業で詳しく説明するにはあまりにも深いものです。さらに掘り下げたい場合は、Niko Matsakis による this blog post を参照してください。次のキーワードも参照してください。

  • ランダムな時間だけスリープする新しい sleeper 構造体を作成し、それを Vec に追加してみてください。