The most natural way to handle this in a way that scales to large function counts with the least amount of boilerplate in your "derived" types:
mod apple_eater {
pub struct AppleEater {
granny_smiths: u32,
golden_delicious: u32,
...
}
impl AppleEater {
pub fn report(&self) {
println!("I ate {} granny smiths!", self.granny_smiths);
println!("...and {} golden delicious!", self.golden_delicous);
...
}
pub fn eat_granny_smith(&mut self) { self.granny_smiths += 1; }
pub fn eat_golden_delicious(&mut self) { self.golden_delicious += 1; }
...
}
pub trait AsAppleEater {
fn as_apple_eater(&self) -> &AppleEater;
fn report(&self)
{ self.as_apple_eater().report() }
}
pub trait AsMutAppleEater: AsAppleEater {
fn as_mut_apple_eater(&mut self) -> &mut AppleEater;
fn eat_granny_smith(&mut self)
{ self.as_mut_apple_eater().eat_granny_smith() }
fn eat_golden_delicious(&mut self)
{ self.as_mut_apple_eater().eat_golden_delicious() }
}
}
Now types only need to implement two functions:
impl AsAppleEater for Teacher {
fn as_apple_eater(&self) -> &AppleEater
{ &self.apple_eater }
}
impl AsMutAppleEater for Teacher {
fn as_mut_apple_eater(&mut self) -> &mut AppleEater
{ &mut self.apple_eater }
}
and as a bonus, you can choose not to implement AsMutAppleEater
for some types:
impl AsAppleEater for PersonWhoJustAte {
fn as_apple_eater(&self) -> &AppleEater
{ &self.apple_eater }
}
// no AsMutAppleEater; the person is too full to eat more!
(similarly, this solves the circle/ellipse problem. A circle can implement AsEllipse
if implemented that way, but it cannot implement AsMutEllipse
)
In the case of single inheritance, this boilerplate can be significantly reduced using Deref/DerefMut
instead of custom traits, as auto-deref will automatically forward all methods. But using Deref
for this purpose is generally discouraged, as Deref
is intended for pointer types. Even things as innocent as impl Deref<Target=CStr> for CString
have led to problems like the ability to call as_ptr()
on a temporary CString
.