I'm building an application. It has one central artifact, I call it the "Brain" that is aware of all subsystems (for example the DB).
I want to not use any specific DB implementation directly, but work with a trait object (or anything that would fulfill the same purpose).
So that would look like
struct Brain {
db: Box<dyn DbTrait>,
}
However, I also want all code to be efficient, so I use tokio, and most stuff is async
trait DbTrait {
fn do_something(&self) -> impl Future<Output = Whatever>;
}
That clashes, as Rust isn't able to create trait objects from traits that return impl
types, as highlighted in the following MWE:
trait ReturnsFutures {
fn futuristic(&self) -> impl Future<Output = ()>;
}
struct FutureReturner {}
impl ReturnsFutures for FutureReturner {
async fn futuristic(&self) {
println!("just fake async");
}
}
struct OtherFutureReturner {}
impl ReturnsFutures for OtherFutureReturner {
async fn futuristic(&self) {
println!("also fakin' it");
}
}
struct FuturisticService {
future_returner: Box<dyn ReturnsFutures>,
}
gives the error
the trait `ReturnsFutures` cannot be made into an object
consider moving `futuristic` to another trait
the following types implement the trait, consider defining an enum where each variant holds one of these types, implementing `ReturnsFutures` for this new enum and using it instead:
FutureReturner
OtherFutureReturnerrustcClick for full compiler diagnostic
Currently, I can think of one workaround, which is instead being async, the trait could return a channel to wait for the result. That may be quite doable, though I feel more complex. Are there any other, more viable/idiomatic solutions to this kind of problem?
The old and tested crate for that is async_trait
. It existed even before async fn in traits (and impl trait in traits) was stabilized, and served as the primary mechanism for async traits back then. It still works fine, but it means you will have to pay the cost of boxing the returned future and dynamically-dispatching it even when you don't need dyn DbTrait
.
dynosaur
is a newer crate, from the Rust language team, that is intended to allow you to dynamically-dispatch traits with async fns/impl traits. With it, you can continue using the trait inside generics as usual without overhead, but it also produces a DynDbTrait
struct that fills the role of Box<dyn DbTrait>