Is async traits working for regular methods on nightly?

I have a backend component that can be one of any number of persistent and durable storage. That looks like a good candidate for a trait. However, one of the storages can be indexDB, and it looks like the APIs are either an async/await (rexie crate) or callback (web-sys crate).

Looks like async in traits is not a feature in stable.

I'm trying out #![feature(async_fn_in_trait)] in nightly, and according to this announcement post, I should be able to do this:

// storage/mod.rs
pub trait Adaptor {
    async fn fetch_data(&self) -> String;
}

// storage/indexeddb.rs
pub struct IndexedDb {
    // ...some fields
}

impl Adaptor for IndexedDb {
    async fn fetch_data(&self) -> String {
        format!("Hello from IndexedDb")
    }
}

However, when I compile, I get:

error[E0038]: the trait `Adaptor` cannot be made into an object
   --> src/indicies.rs:123:16
    |
123 |     store: Box<dyn Adaptor>,
    |                ^^^^^^^^^^^ `Adaptor` cannot be made into an object
    |
note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
   --> src/storage/mod.rs:14:14
    |
13  | pub trait Adaptor {
    |           ------- this trait cannot be made into an object...
14  |     async fn fetch_data(&self) -> String;
    |              ^^^^^^^^^^ ...because method `fetch_data` is `async`
    = help: consider moving `fetch_data` to another trait

For more information about this error, try `rustc --explain E0038`.

I'm running

➜ rustc --version
rustc 1.70.0-nightly (39f2657d1 2023-03-09)
➜ rustup default
nightly-aarch64-apple-darwin (default)

However, I was able to get a static method on the trait to compile if it had where Self: Sized bound is attached to the method.

Question 1: Is async_fn_in_trait only working for static async methods? Or should it be working for regular methods as well? According to the announcement post, it seems like it should be.

Question 2: Taking a step back, is there a way around this without async traits?

Did you see this part in that announcement?

Limitation: Dynamic dispatch

There's one final limitation: You can't call an async fn with a dyn Trait. Designs to support this exist4, but are in the earlier stages. If you need dynamic dispatch from a trait, you're better off using the async_trait macro for now.


Update: for those who are curious about AFIT, there are a few posts from Eric Holk other than Niko's dyn async traits series

Thanks for the highlight. It didn't register in my head somehow.

As a followup, how would you even use a trait without dynamic dispatch? Is it that you're mostly working with a concrete type, and you're just using traits to enforce an interface, rather than as a way to treat it like a generic type with a common interface?

By requiring it as a bound in generic functions - fn use_adaptor<T: Adaptor>(adaptor: T) is allowed to call adaptor.fetch_data, and you can pass any specific implementation of Adaptor there (which might be chosen at runtime, for example, or switched with the feature flag).

2 Likes

Ah, thanks for the tip. I was a bit perplexed when having the generic "infects" the containing structs all the way up. This was problematic since wasm_bindgen can't have generic structs exposed. I just worked around it by making the exposed struct use a concrete class for the store.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.