#Async fn inside a dyn trait

11 messages · Page 1 of 1 (latest)

proven bobcat
#

Hey, I am trying to write a dyn trait which has two functions inside that are async and two sync ones.
They look like this:

pub trait Provider {
    fn id(&self) -> &str;
    fn name(&self) -> &str;
    async fn fetch_data(&self, game_id: &str) -> Result<ApiData, MinesweeperError>;
    async fn fetch_name(&self, uuid: &str) -> Result<PlayerData, MinesweeperError>;
}

later in the code, I then want to select the appropriate provider based on the ID and run the async fetch functions:

let possible_providers: Vec<&dyn Provider> = vec![&GreevProvider, &McPlayHdProvider];

let optional_provider = possible_providers
    .iter()
    .find(|x| x.id() == provider.as_str());

let Some(provider) = optional_provider else {
    return Response::error("Unknown Provider", 400);
};

let result_api_data = provider.fetch_data(game_id).await;

But when compiling, I get this error:

the trait `Provider` cannot be made into an object

Full error: https://pastebin.com/raw/SeebEGqN

And honestly, I have no clue on what exactly I would have to do different, to get something like I intended to have. The compiler tells me to move my async functions into a different trait and implement an enum. However, I don't exactly know what that would solve or better said how that would solve my issue.

soft tartan
#

short answer is you can't: https://blog.rust-lang.org/2023/12/21/async-fn-rpit-in-traits.html

Dynamic dispatch
Traits that use -> impl Trait and async fn are not object-safe, which means they lack support for dynamic dispatch. We plan to provide utilities that enable dynamic dispatch in an upcoming version of the trait-variant crate.
#

async functions are not normally object safe because the type of future they return depends on each implementation

#

your function have to return boxed future to make dynamic dispatch work; the async_trait handles that

#

and the compiler is telling you that if the set of implementors is known in advance you can replace dynamic dispatch with static dispatch, e.g.

enum Providers {
    Greev(GreevProvider),
    McPlayHd(McPlayHdProvider),
}

and just match everwhere

#
let possible_providers: Vec<Providers> = vec![Providers::Greev(GreevProvider), Providers::McPlayHd(McPlayHdProvider)];

let optional_provider = possible_providers
    .iter()
    .find(|x| match x { 
        Greev(x) => x.id() == provider.as_str(),
        McPlayHd(x) => x.id() == provider.as_str(),
    });
#

kinda ugly but it would work

#

and you could even implement all of the methods on enum Providers itself:

impl Provider for Providers {
    fn id(&self) -> &str {
        match self {
            Providers::Greev(provider) => provider.id(),
            Providers::McPlayHd(provider) => provider.id(),
        }
    }

    fn name(&self) -> &str {
        match self {
            Providers::Greev(provider) => provider.name(),
            Providers::McPlayHd(provider) => provider.name(),
        }
    }

    async fn fetch_data(&self, game_id: &str) -> Result<ApiData, MinesweeperError> {
        match self {
            Providers::Greev(provider) => provider.fetch_data(game_id).await,
            Providers::McPlayHd(provider) => provider.fetch_data(game_id).await,
        }
    }

    async fn fetch_name(&self, uuid: &str) -> Result<PlayerData, MinesweeperError> {
        match self {
            Providers::Greev(provider) => provider.fetch_name(uuid).await,
            Providers::McPlayHd(provider) => provider.fetch_name(uuid).await,
        }
    }
}
#

then your original code should work unmodified (except for the initial creation of possible_providers)

proven bobcat