Trait-variant crate now available for AFIT and RPITIT

Rust 1.75, to be released December 28, contains support for async fn and -> impl Trait in traits (AFIT and RPITIT, respectively).

pub trait HttpService {
    // Desugars to:
    // fn fetch(&self, url: Url) -> impl Future<Output = HtmlBody>
    async fn fetch(&self, url: Url) -> HtmlBody;
}

However, if you try to use async fn in a public trait, you will get a warning:

warning: use of `async fn` in public traits is discouraged as auto trait bounds cannot be specified
 --> src/lib.rs:7:5
  |
7 |     async fn fetch(&self, url: Url) -> HtmlBody;
  |     ^^^^^
  |
  = 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
  |
7 -     async fn fetch(&self, url: Url) -> HtmlBody;
7 +     fn fetch(&self, url: Url) -> impl std::future::Future<Output = HtmlBody> + Send;
  |

This warning is asking you to choose whether you want your trait to be compatible with work-stealing executors. Without Send bounds on the trait itself, you couldn't write code like this.

fn spawn_task(service: impl HttpService + 'static) {
    // error: future cannot be sent between threads safely!
    tokio::spawn(async move {
        let url = Url::from("https://rust-lang.org");
        let _body = service.fetch(url).await;
    });
}

trait-variant by the Rust Async Working Group provides a convenient proc macro to provide both Send and non-Send variants of a trait. Add it to your project with cargo add trait-variant.

#[trait_variant::make(HttpService: Send)]
pub trait LocalHttpService {
    // In the HttpService variant, returns impl Future + Send.
    async fn fetch(&self, url: Url) -> HtmlBody;
}

After replacing the original trait definition with the one above, the warning goes away and spawn_task compiles without modification. Applications that do not use work-stealing executors can use the original LocalHttpService trait, which allows non-Send futures.

I would like to include this crate in the official announcement of AFIT and RPITIT later this week. Please try it out and leave feedback if you have a use for it. Thank you!

10 Likes

What is the MSRV policy?

Right now I expect the MSRV to remain at 1.75. The next release will include this in the manifest. In the near term all I expect to add is a utility for dynamic dispatch with these traits, which should not require any new features.

My hope is to one day transition to implementable trait aliases, at which point we could either allow trait authors to transition manually (say with a new macro, #[trait_variant::alias]), or upgrade to a new version of the crate (possibly 2.0) which has a higher MSRV. I haven't thought through all of the semver implications here, but I think either path seems manageable.

3 Likes