Async trait wouldn't compile without async_trait crate

I have SummaryExt that has async fn summary(), I couldn't make it compile without using #[async_trait]. I thought my use case for async trait has been stabilized in Rust 1.75?

From the async_trait crate

The stabilization of async functions in traits in Rust 1.75 did not include support for using traits containing async functions as dyn Trait .

Does the error caused by the boxed error Box<dyn Error + Send + Sync + 'static> and I still have to use the async_trait crate?

// use async_trait::async_trait;
use std::error::Error;

use chrono::{DateTime, Local};
use futures::{
    future::try_join_all,
    stream::{self, StreamExt},
};

#[derive(Clone, Copy, Default)]
struct DateTimeRange {
    start: DateTime<Local>,
    end: DateTime<Local>,
}

// #[async_trait]
trait SummaryExt {
    // type Output: Send + Sync + 'static;
    type Output;

    async fn summary() -> Result<Vec<Self::Output>, Box<dyn Error + Send + Sync + 'static>>;
    // fn summary() -> impl std::future::Future<Output = Result<Vec<Self::Output>, Box<dyn Error + Send + Sync + 'static>>> + std::marker::Send;
}

async fn do_work<T: SummaryExt + 'static>(ranges: Vec<DateTimeRange>) -> Vec<(DateTimeRange, Vec<T::Output>)>
where
    T::Output: Send + Sync + 'static,
{
    let futures = stream::iter(&ranges)
        .map(|_| tokio::spawn(T::summary()))
        .collect::<Vec<tokio::task::JoinHandle<_>>>()
        .await;

    try_join_all(futures).await;
    Vec::new()
}

error[E0277]: `impl futures::Future<Output = Result<Vec<<T as SummaryExt>::Output>, Box<(dyn std::error::Error + std::marker::Send + Sync + 'static)>>>` cannot be sent between threads safely
   --> src/lib.rs:30:31
    |
30  |         .map(|_| tokio::spawn(T::summary()))
    |                  ------------ ^^^^^^^^^^^^ `impl futures::Future<Output = Result<Vec<<T as SummaryExt>::Output>, Box<(dyn std::error::Error + std::marker::Send + Sync + 'static)>>>` cannot be sent between threads safely
    |                  |
    |                  required by a bound introduced by this call
    |
    = help: the trait `std::marker::Send` is not implemented for `impl futures::Future<Output = Result<Vec<<T as SummaryExt>::Output>, Box<(dyn std::error::Error + std::marker::Send + Sync + 'static)>>>`
note: required by a bound in `tokio::spawn`
   --> /playground/.cargo/registry/src/index.crates.io-6f17d22bba15001f/tokio-1.39.2/src/task/spawn.rs:167:21
    |
165 |     pub fn spawn<F>(future: F) -> JoinHandle<F::Output>
    |            ----- required by a bound in this function
166 |     where
167 |         F: Future + Send + 'static,
    |                     ^^^^ required by this bound in `spawn`
help: `std::marker::Send` can be made part of the associated future's guarantees for all implementations of `SummaryExt::summary`
    |
21  -     async fn summary() -> Result<Vec<Self::Output>, Box<dyn Error + Send + Sync + 'static>>;
21  +     fn summary() -> impl std::future::Future<Output = Result<Vec<Self::Output>, Box<dyn Error + Send + Sync + 'static>>> + std::marker::Send;

#[async_trait] chooses to make all futures from async fn required to be Send + Sync. tokio::spawn requires spawned tasks to be Send. Native support for async fn in traits does not place the requirement for the future to be Send + Sync, and there is not yet a way to bound the future to be so without desugaring the async fn or continuing to use the #[async_trait] attribute.

This limitation is a major contributor to why the compiler will warn you if you write a pub trait with async fn.

warning: use of `async fn` in public traits is discouraged as auto trait bounds cannot be specified
  --> src/lib.rs:47:5
   |
47 |     async fn f();
   |     ^^^^^
   |
   = note: you can suppress this lint if you plan to use the trait only in your own code, or do not care about auto traits like `Send` on the `Future`
   = note: `#[warn(async_fn_in_trait)]` on by default
help: you can alternatively desugar to a normal `fn` that returns `impl Future` and add any desired bounds such as `Send`, but these cannot be relaxed without a breaking API change
   |
47 -     async fn f();
47 +     fn f() -> impl std::future::Future<Output = ()> + Send;
   |
1 Like