Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Rust trait state

I'll start with this monster "Monster" code from Rust for Rubyist:

trait Monster {
    fn attack(&self);
    fn new() -> Self;
}

struct IndustrialRaverMonkey {
    life: int,
    strength: int,
    charisma: int,
    weapon: int,
}

struct DwarvenAngel {
    life: int,
    strength: int,
    charisma: int,
    weapon: int,
} ...
impl Monster for IndustrialRaverMonkey { ...
impl Monster for DwarvenAngel { ...

I worry about duplication of code. In Java I would create interface that define attack method and base class with all that parameters (life, strength, charisma, weapon). I will do same thing in C++ with abstract class. I can find some ugly and unintuitive ways to work around this problem, but is there a good way to reduce code? I mean, to keep it scalable and readable.

like image 900
Ivan Ivanov Avatar asked Jun 14 '26 22:06

Ivan Ivanov


2 Answers

Another approach, which favors composition, and from which is easier to diverge implementations if needed (e.g., the Characteristics for DwarvenAngel require an additional field):

trait Monster {
    fn attack(&self);
}

struct Characteristics {
    life: int,
    strength: int,
    charisma: int,
    weapon: int,
}

struct IndustrialRaverMonkey {
    characteristics: Characteristics
}

struct DwarvenAngel {
    characteristics: Characteristics
}

fn same_attack(c: Characteristics) {
    fail!("not implemented")
}

impl Monster for IndustrialRaverMonkey {
    fn attack(&self) {
        same_attack(self.characteristics)
    }
}

impl Monster for DwarvenAngel {
    fn attack(&self) {
        same_attack(self.characteristics)
    }
}

Or, you can have an enum represent your monster types, very similar to A.B.'s answer:

trait Monster {
    fn attack(&self);
}

struct Characteristics {
    life: int,
    strength: int,
    charisma: int,
    weapon: int,
}

enum Monsters {
    IndustrialRaverMonkey(Characteristics),
    DwarvenAngel(Characteristics),
}

fn same_attack(_: &Characteristics) {
    fail!("not implemented")
}

impl Monster for Monsters {
    fn attack(&self) {
        match *self {
            IndustrialRaverMonkey(ref c) => same_attack(c),
            DwarvenAngel(ref c)          => same_attack(c)
        }
    }
}
like image 127
Ionuț G. Stan Avatar answered Jun 17 '26 22:06

Ionuț G. Stan


Would you consider something like this an acceptable solution?

trait Actor {
    fn attack(&self);
}

enum MonsterId {
    IndustrialRaverMonkey,
    DwarvenAngel
}

struct Monster {
    life: int,
    strength: int
}

impl Monster {
    fn new(id: MonsterId) -> Monster {
        match id {
            IndustrialRaverMonkey => 
            Monster { life: 12, strength: 8 },

            DwarvenAngel => 
            Monster { life: 18, strength: 12 }
        }
    }
}


impl Actor for Monster { 
    fn attack(&self) {}
}

Updated with a better example.

like image 25
A.B. Avatar answered Jun 17 '26 22:06

A.B.