Taking a similar approach to what @trentj proposed, you can go a long way in doing pseudo-OO in Rust:
Please appreciate how neat the field accesses are working out...
use std::ops::{Deref, DerefMut};
use derive_more::{Deref, DerefMut};
use enum_dispatch::enum_dispatch;
#[derive(Clone, Debug)]
struct StandardFields {
field1: i32,
field2: i32,
}
#[derive (Deref, DerefMut, Debug, Clone)]
struct Standard(StandardFields);
// implementations for standard in here
#[enum_dispatch]
trait ApplicationTrait: DerefMut<Target = StandardFields> {
fn do_something(&self) {
println!("doing standard");
}
fn modify_some_fields_to(&mut self, x: i32) {
self.field1 = x;
}
fn some_other_method_without_overrides(&self) {
println!("hello from other method");
}
}
impl ApplicationTrait for Standard {}
#[derive(Deref, DerefMut, Debug)]
struct Pro {
#[deref(forward)]
#[deref_mut(forward)]
standard: Standard,
extra_pro_field1: i32,
pro_field2: i32,
}
// override whatever you like for pro
impl ApplicationTrait for Pro {
fn do_something(&self) {
println!("doing pro");
self.standard.do_something();
}
fn modify_some_fields_to(&mut self, x: i32) {
self.field2 = x;
self.standard.modify_some_fields_to(x);
self.pro_field2 = x;
}
}
fn main() {
let app1 = Standard(StandardFields{
field1: 42,
field2: 100,
});
let app2 = Pro {
standard: app1.clone(),
extra_pro_field1: 1337,
pro_field2: 0,
};
let mut apps = [Application::from(app1), Application::from(app2)];
for app in &mut apps {
app.do_something();
app.modify_some_fields_to(999);
app.some_other_method_without_overrides();
print!("{:?}\n\n", app);
}
}
#[enum_dispatch(ApplicationTrait)]
#[derive(Debug)]
enum Application {
Standard(Standard),
Pro(Pro),
}
// couldn’t find any crate to derive this kind of impl
impl Deref for Application {
type Target = StandardFields;
fn deref(&self) -> &StandardFields {
use Application::*;
match self {
Standard(s) => s.deref(),
Pro(p) => p.deref(),
}
}
}
impl DerefMut for Application {
fn deref_mut(&mut self) -> &mut StandardFields {
use Application::*;
match self {
Standard(s) => s.deref_mut(),
Pro(p) => p.deref_mut(),
}
}
}
Output:
doing standard
hello from other method
Standard(Standard(StandardFields { field1: 999, field2: 100 }))
doing pro
doing standard
hello from other method
Pro(Pro { standard: Standard(StandardFields { field1: 999, field2: 999 }), extra_pro_field1: 1337, pro_field2: 999 })
One detail to note here (click me to expand/collapse):
When you call down to the same (or a different) standard method via .standard.method()
, that method cannot "call up" to other pro methods anymore. If you, however, call "horizontally" to a different method via self.method()
from pro, that method can in turn call other pro methods, even if Pro
doesn’t implement the method itself but its implementation comes from the standard/default implementation. The default implementation gets duplicated in this case into two versions: the standard version, accessing other standard methode, and the pro version, accessing other pro version methods. If e.g. you wanted do_something
to call another method that needs to behave like the pro version on pro, you can split it up like e.g.
// in trait ApplicationTrait:
fn __do_something(&self) {
println!("doing standard");
self.modify_some_field_to(42); // supposed to have different behavior on pro
}
fn do_something(&self) { self.__do_something() }
// in impl ApplicationTrait for Pro:
// don’t implement __do_something
fn do_something(&self) {
println!("doing pro");
self.__do_something(); // calls standard version with access to pro
}
If you always want this behavior, just don’t use the .standard
field explicitly at all and always introduce a duplicate method. (I suppose, someone could write a macro for this kind of stuff, too....)
Edit: Since this thread is about "idiomatic" ways, I have to mention that my code above might not be the most idiomatic Rust code.