2
votes

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:

  1. Allowing Poodle and Rottweiler to bark using the exact same behavior which the breeds should not need to regard.
  2. Allowing this to be possible without recoding bark() in every breed module, which is programming hell as it leads to repetitious code and every module has to implement bark().
  3. Traits are the inverse and cannot access the struct, so default-trait implements do not work. Rust does not support OOP-like inheritance and nor is it desired here.

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.

1
The simple fact that you have the Dog, Poddle and Rottweiler structs means you made an initial design error, though: this is an OOP thinking, while Rust isn't based on OOP principles. You should start the design from the desired features rather than from an object view. If we start from your structs and add the Barking property, it soon starts to look as nothing which would exist in a real Rust code. - Denys Séguret
I don't get how you meant to relate your Dog and Poodle/Rottweiler. Do you want each breed to contain a Dog? Do you want Dog to have a breed that is a tagged union? Something else? - Bergi
We cannot fix the solution without knowing the problem. This code doesn't solve a problem, it's a toy example that in some other language would demonstrate inheritance. But Rust doesn't have inheritance, so it's a fish out of water; it's useless. When designing programs you must start with requirements and constraints: not half-baked ideas that work in some other language. You can't translate structural designs from other languages. It does not work. Describe the actual problem you are trying to solve. - trentcl
Re: "My actual issue is giving dog breeds barking without repeating code" That is not your problem, that's the solution which you have imagined must exist. But it doesn't. You've already trapped yourself into what is most likely a bad design by making assumptions like "Poodle must be a struct" and "bark must be a method". Again, requirements and constraints: what does this program do besides demonstrate a bad design? - trentcl
Even in nature itself, the "inheritance" analogy is flawed. Fifi doesn't bark the way she does because she is a Poodle, but because she has a VoiceBox which is of a particular shape and size and also has a BarkingBehavior that she learned from other dogs. Poodle is an idea purely in the minds of some humans who find it easier to understand nature by pretending that sharp dividing lines exist where, in reality, none do. - trentcl

1 Answers

7
votes

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).