I don't end up writing inheritance-like code in Rust very much, but I usually reach for this sort of pattern when I do:
struct Base<Ext:?Sized = ()> {
value: u32,
ext: Ext
}
struct NameExt {
name: String
}
type NamedBase = Base<NameExt>;
impl<Ext:?Sized> Base<Ext> {
fn get_value(&self) -> u32 { self.value }
}
impl NamedBase {
fn get_name(&self)->&str { self.ext.name.as_str() }
}
This way, NamedBase
is-a Base
in a very real sense that the Rust compiler understands. It's also pretty close to how inheritance is implemented in C++. If you want various Ext
s to influence the behavior of Base
's methods, you can define a trait for them and choose between static and virtual dispatch as appropriate for your application:
trait BaseExt { ... }
impl<Ext:?Sized> Base<Ext> {
fn reqires_delegate(&self) where Ext:BaseExt { ... }
}
Edit: Thinking about this some more, the most significant drawbacks to this approach are:
- All types are final by default, and must opt-in to be extended. This is different from C++, but feels like it fits the Rust design philosophy
- The extension trait can be quite awkward, especially if the method definitions need access to the base object's properties. This can be improved with something like object-safe
arbitrary_self_types
: This lets the trait include methods that takeself: &Base<Self>
and then allows them to be called via&Base<dyn BaseExt>