For writing a very large program, I see no way to alleviate having to write the same code for each struct that uses a certain shared behaviour.
For example, Dog may "bark":
struct Dog {
is_barking: bool,
....
}
impl Dog {
pub fn bark(self) {
self.is_barking = true;
emit_sound("b");
emit_sound("a");
emit_sound("r");
emit_sound("k");
self.is_barking = false;
}
....
}
And many breeds of this dog may exist:
struct Poodle {
unique_poodle_val: &str
}
impl Poodle {
pub fn unique_behaviour(self) {
self.some_behaviour();
}
}
struct Rottweiler {
unique_rottweiler_val: u32
}
impl Rottweiler{
pub fn unique_behaviour(self) {
self.some_behaviour();
}
}
The issue is that Rust seems incapable of this in my current knowledge, but it needs to be done and I need a workaround for:
Therefore, I ask: How would it be possible to implement bark() without rewriting bark() in each module, since Poodle and Rottweiler bark exactly the same way after all?
Please provide an example of how to solve this issue in Rust code, idiomatic and slightly hacky solutions are welcome but please state which they are as I am still learning Rust. Thank you.
Edit: The boolean is not a thread thing, rather it's a example of setting some state before doing something, i.e. emit_sound is within this state. Any code we put into bark() has the same issue. It's the access to the struct variables that is impossible with say, traits.
You've put the finger on something that Rust doesn't do nicely today: concisely add to a struct a behavior based on both internal data and a function.
The most typical way of solving it would probably be to isolate Barking in a struct owned by all dogs (when in doubt, prefer composition over inheritance):
pub struct Barking {
is_barking: bool,
}
impl Barking {
pub fn do_it(&mut self) {
self.is_barking = true; // <- this makes no sense in rust, due to the ownership model
println!("bark");
println!("a");
println!("r");
println!("k");
self.is_barking = false;
}
}
struct Poodle {
unique_poodle_val: String,
barking: Barking,
}
impl Poodle {
pub fn unique_behaviour(self) {
println!("some_behaviour");
}
pub fn bark(&mut self) {
self.barking.do_it();
}
}
struct Rottweiler {
unique_rottweiler_val: u32,
barking: Barking,
}
impl Rottweiler{
pub fn unique_behaviour(self) {
println!("unique behavior");
}
pub fn bark(&mut self) {
// maybe decide to bite instead
self.barking.do_it();
}
}
In some cases it can make sense to define a Barking trait, with a common implementation and declaring some functions to deal with the state:
pub trait Barking {
fn bark(&mut self) {
self.set_barking(true);
println!("bark");
println!("a");
println!("r");
println!("k");
self.set_barking(false);
}
fn set_barking(&mut self, b: bool);
}
struct Poodle {
unique_poodle_val: String,
is_barking: bool,
}
impl Poodle {
pub fn unique_behaviour(self) {
println!("some_behaviour");
}
}
impl Barking for Poodle {
fn set_barking(&mut self, b: bool) {
self.is_barking = b;
}
}
Beware that this half-OOP approach often ends up too much complex and less maintainable (like inheritance in OOP languages).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With