How to share same implementation and maybe share fields
Asked Answered
E

3

7

How can I simplify this code? I still can't wrap my head around rust's traits and structs after OOP.

struct Player {
    entity: Entity,
    hp: i32,
    atk: i32
}

struct Chest {
    entity: Entity,
    amount: i32
}

impl Drawable for Chest {
    fn draw(&self, mut pencil: Pencil) {
        pencil.draw(&self.entity);
    }
}

impl Drawable for Player {
    fn draw(&self, mut pencil: Pencil) {
        pencil.draw(&self.entity);
    }
}

Maybe there is a way to inherit some fields like in OOP?

Also, if anybody knows a good and clear tutorial about traits and structs in rust, I would be very glad if you shared it!

Exorcist answered 21/4, 2022 at 20:24 Comment(1)
Rust doesn't have implementation sharing (e.g., inheritance in OOP, struct embedding in Go). The preferred way to solve this issue in Rust is just plain old composition (of modular parts). Rust's structs and enums are pretty powerful so you won't be missing out much.Alectryomancy
M
8

In my experience, typically when you want to "share attributes" like this you typically want to break the structs into their own types and implement the traits you need on each.

Consider if your structs looked like this:

struct DrawableEntity {
    entity: Entity,
    ... // other stuff you might need to draw
}

struct Chest {
    drawable: DrawableEntity,
    ...
}

struct Player {
    drawable: DrawableEntity,
    ...
}

impl Drawable for DrawableEntity { ... }

Then in your code it might look like this:

player.drawable.draw(mut pencil);
chest.drawable.draw(mut pencil);
Manure answered 21/4, 2022 at 21:30 Comment(0)
B
1

Since your impl blocks do the exact same thing with a different type, you can abstract that out into a macro so that is is trivial to repeat for any new Drawable types:

macro_rules! impl_drawable {
    ($t:ty) => {
        impl Drawable for $t {
            fn draw(&self, mut pencil: Pencil) {
                pencil.draw(&self.entity);
            }
        }
    };
}

impl_drawable!(Chest);
impl_drawable!(Player);

However, that is about all I think you can do, traits can't have fields like an abstract class in an OOP language would, so you can't really "share" the idea of having an Entity in a struct.

If you haven't read this already, the book has a chapter that talks a bit about common OOP patterns and how they translate to idiomatic Rust.

Boorish answered 21/4, 2022 at 20:47 Comment(0)
F
1

We can find the example in the rust book at chapter 16.2 "Returning traits with dyn".

Here is some sample code based on the question. I put the functions with the Answer to the question at the top of the file so it's easier to read for newcomers.

fn main() {
//Initialise game
//New empty struct
let pencil = Pencil {}; 

//We need Box <dyn TraitName> because Drawable can be: Player or a Chest, here it's a player
let player: Box<dyn Drawable> = create_player_or_chest(true);

//We need Box <dyn TraitName> because Drawable can be: Player or a Chest, here it's a chest
let chest: Box<dyn Drawable> = create_player_or_chest(false);

//Play game
player.draw(&pencil);
chest.draw(&pencil);

}

fn create_player_or_chest(is_player:bool) -> Box<dyn Drawable > {
    if is_player {
        println!("GAME INIT: Creating a new player");
        Box::new(Player {
            hp: 100,
            atk: 2,
            entity: Entity {
                name: String::from("Mage")
            }
        })
    } else {
        println!("GAME INIT: Creating a new chest");
        Box::new(Chest {
            amount: 1,
            entity: Entity {
                name: String::from("Withering staff")
            }
        })
    }
}

impl Drawable for Chest {
    fn draw(&self, pencil: &Pencil) {
        println!("Drawing chest with amount of items: {}", self.amount);
        pencil.draw(&self.entity);
    }
}

impl Drawable for Player {
    fn draw(&self, pencil:&Pencil) {
        println!("Drawing player with hp: {} and atk: {}", self.hp, self.atk);
        pencil.draw(&self.entity);
    }
}

impl Pencil {
    fn draw(&self, entity: &Entity) {
        println!("{}", entity.name);
    }
}

trait Drawable {
    fn draw(&self, pencil: &Pencil);
}
struct Entity {
    name: String,
}

struct Player {
    entity: Entity,
    hp: i32,
    atk: i32
}

struct Chest {
    entity: Entity,
    amount: i32
}

struct Pencil {}
Fredericfrederica answered 25/10, 2023 at 8:22 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.