Fairly often in other languages, it's helpful for a library provider to provide an abstract base class that partially or fully implements an interface, when users of the library will want to provide their own implementations. Users can then override and call the provided implementations to customize behavior.
There is no such thing in rust. Default method implementations in traits are useful, but they are not a solution when the "base class" requires state.
What do you all do when you want to provide a similar facility to users of your library?
Let me tell you what I settled on:
I define a trait that library users will have to implement:
pub trait MyTrait { fn doTheThing(&self); } I define a "base class" that provides the "base class" implementation:
pub struct MyTraitBase { ... } impl MyTraitBase {p ... } Then I define a trait that library users can implement to use the base class:
pub trait MyTraitImpl { fn get_delegate(&self): &MyTraitBase; fn doTheThing(&self) { self.get_delegate().doTheThing(); } } Now, what I really wanted to do at this point was to provide a blanket implementation of MyTrait for anything that implements MyTraitImpl,which would have completed the solution. Unfortunately, coherence rules disallow this, and there seems to be NO REASONABLE WAY to do anything equivalent.
So in the end I produced a whole crate to solve the problem once and for all in my own quirky way, so library users can write an implementation like this:
#[delegateTrait(MyTraitImpl)] pub struct UserStruct { ... } impl MyTraitImpl for UserStruct { ... } The attribute macro implements DelegateProvider for UserStruct, so I can provide blanket implementations of whatever I want like this:
impl<T> MyTrait for T where T: DelegateProvider, for<'a> DelegateRef<'a, T::Target>: MyTrait, { fn doTheThing(&self) { // delegate_ref() returns &dyn MyTraitImpl self.delegate_ref().doTheThing(); } } impl MyTrait for DelegateRef<'_, dyn MyTraitImpl> { fn doTheThing(&self) { self.target.doTheThing(); } } And the user's struct will get the MyTrait implementation. This allows me to leverage any user's trait implementation into any new trait implementation I want, and provides flexibility pretty much equivalent to abstract base classes, allowing multiple base classes for the same trait, etc.
It works pretty well, but why did I have to publish a crate? How can we be this far into rust without a more idiomatic way to do this?
17 posts - 5 participants
