Closed audunhalland closed 2 years ago
Note: This is also possible to do using static dispatch. ~It's much simpler~, and the Backend
trait is just a verbatim copy of the Facade
trait. The Facade
implementation very simply delegates with a Self: Backend
bound. The only constraint is that there can only be one implementation of Backend
for Impl<T>
.
Static:
#[entrait(pub Backend)]
pub trait Facade {}
Dynamic:
#[entrait(pub dyn Backend)]
pub trait Facade {}
Impl modules:
#[entrait_impl(Backend)]
#[entrait_impl(pub MyImpl: dyn Backend)]
Edit: This could be problematic because of coherence rules. I.e. we cannot implement Backend
for Impl<T>
in a crate that is downstream to both :(
So the implementing crate has to introduce a new type into the mix, just like in the dyn
example. The application has to statically "link" this type in via implementing an associated type on some entrait-trait.
The only solution I found (so far) that allows static dispatch is this, which involves 3 traits:
use implementation::Impl;
pub trait Facade {
fn foo(&self) -> i32;
}
// Implement in intermediate layer:
pub trait FacadeImpl<T> {
fn foo(__self: &Impl<T>) -> i32;
}
// Implement in app layer ("main.rs")
pub trait FacadeSelectImpl<T> {
type Impl: FacadeImpl<T>;
}
// Delegation - implemented here, in "core"
impl<T> Facade for Impl<T> where T: FacadeSelectImpl<T> {
fn foo(&self) -> i32 {
<T as FacadeSelectImpl<T>>::Impl::foo(self)
}
}
Solution exploration in this repo: https://github.com/audunhalland/entrait-dependency-inversion. There is a way to do it using one trait, but use custom smart pointers.
Implemented in #10 .
The final syntax is
#[entrait(TraitImpl, delegate_by = DelegationTrait)]
trait Trait {
fn foo(&self);
}
I was not able to get zero-cost futures working when delegating using an alternative implementation of the original trait, .e.g. for SomeImplRef<'s, T>(&'s Impl<T>)
. I wasn't able to make the resulting future implement Send
.
The solution is to generate the TraitImpl
, consisting of static methods:
trait TraitImpl<T> {
fn foo(_impl: &Impl<T>);
}
The implementation block looks like:
#[entrait_impl]
mod my_impl {
pub fn foo(deps: &impl Any) {
}
#[derive_impl(super::TraitImpl)]
pub struct MyImpl;
}
We choose the implementation by writing:
impl DelegationTrait<Self> for App {
type Target = my_impl::MyImpl;
}
Released in 0.4.4
TL;DR: The crate that defines an interface should not need to be the crate that implements it. The way entrait is designed, using delegating blanket implementations, requires some more delegation "magic".
The entrait-for-trait feature
#[entrait(delegate_by = Borrow)]
only works for leaf dependencies.Here is an idea for how to do something similar, but without exiting the
Impl
layer.We have some API that we want to potentially implement (downstream) in different dynamic ways:
We want to implement this trait for
Impl<T>
so it can be used as a dependency. But that, by definition, is the only implementation. We want to have a delegation through dynamic dispatch to reach the final destination. Since that trait is already implemented forImpl<T>
, we need another trait! We can use the entrait syntax to generate a trait from a trait 😬 Let's call the new traitBackend
:The generated trait is generic, and has two receivers:
The generated delegation looks like this:
Now the application has to implement
Borrow<dyn Backend<Self>>
. This is the manual part.To define an implementation of
Backend<T>
, we can write the following module:The functions inside this module need to match the backend interface. I think the compile errors will be good enough. The implementation is of course auto-generated:
The Borrow part of the application could either be a Box or some enum. Avoiding allocation could look like this: