Alternative to the inheritance

For example, I have two structs HumanPlayer and ComputerPlayer. Both of them have similar fields:

name: String,
score: u32,
bonus_card: u32,

They also have similar functionality except for the function fn pick_card(&self), for the HumanPlayer it asks the user if he/she wants to pick a card. For the ComputerPlayer the decision is done using another helper function which uses some logic to decide if it needs extra card or not. Let's call this extra function fn pick_or_pass(&self, human_score: HumanPlayer) -> bool, which is only available for a ComputerPlayer.

With inheritance it's straightforward. I'll use the abstract class Player and both, Human and Computer, will inherit from it, and I'll just add an extra function for Computer and override the other one.

However, what is the optimal solution to this in Rust? just write similar boilerplate code for both structs separately? and create a trait, which will have general implementation of the shared functions?

Or define a field HumanPlayer inside ComputerPlayer? and then if I want to call something from main I'll call something like self.player.get_score(); ?

Any suggestions - best practices?

Thanks!

What's wrong with making a Player trait?

So create Human and Computer structs and have trait Player which will have shared functions. Then implement functions that behavior differently separately for each struct?

Are you just trying to deduplicate code inside the methods on both types, or do you also need functions that can take either type of player as argument?

Yes, the primary goal is to reduce code repetition. But for the next part, the game logic will require to take both HumanPlayer and ComputerPlayer

Well you could certainly give traits a try. You can define accessors for the fields and then define your common code as default methods on the trait.

1 Like

It seems unlikely that you will encounter an open set of types that implement player. It's either going to be human or computer, so you might also try an enum for this case. Consider a top-level Player struct that contains a PlayerType enum, which handles differentiating between humans and computers.

struct Player {
    name: String,
    score: u32,
    bonus_card: u32,
    type: PlayerType,
}

impl Player {
    fn pick_card(&self) -> Card {
        self.type.pick_card()
    }
}
enum PlayerType { 
    Human,
    Computer,
}

impl PlayerType {
    fn pick_card(&self) {
        match self {
            PlayerType::Human => { ... }
            PlayerType::Computer => { ... }
    }
}```
4 Likes