Best way to reuse partial trait implementation?

I'm a newbie to Rust language.
Currently I'm considering what is a best way to reuse partial trait implementation between structures.

When I define Shape trait like this:

trait Shape {
    fn has_corner(&self) -> bool;
    //Color is defined in somewhere.
    fn color(&self) -> Color;
    fn area(&self) -> i32;
    fn move(&self, x: i32, y: i32);
}

And I want to define Red Polygon structures (Rectangle, Pentagon, Hexagon...etc).
I'm already known these structures color (Red!) and they have a corner.
So I want to avoid implementing has_corner and color repeatedly.
But I want to implementing Shapes that has no corner too, so I can't define default implementation for Shape.

What is a best way to write these structures in Rust?

Just curious, but would a macro work here? Maybe just add a red_with_corners!() statement in each implementation of shape.

@cbreeden Thank you for your comment!

It works!

enum Color {
    Red,
    Blue,
    Green
}

trait Shape {
    fn has_corner(&self) -> bool;
    fn color(&self) -> Color;
    fn area(&self) -> i32;
    fn mv(&mut self, dx: i32, dy: i32);
}

macro_rules! red_polygon {
    () => (
        fn has_corner(&self) -> bool {
            return true;
        }
        
        fn color(&self) -> Color {
            return Color::Red;
        }
    )
}

pub struct RedRectangle {
    width: i32,
    height: i32,
    x: i32,
    y: i32
}

impl Shape for RedRectangle {
    red_polygon!();
    
    fn area(&self) -> i32 {
        return self.width * self.height;
    }
    
    fn mv(&mut self, dx: i32, dy: i32) {
        self.x += dx;
        self.y += dy;
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use Shape;
    #[test]
    fn it_works() {
        let rect = RedRectangle { width: 100, height: 100, x: 0, y: 10 };
        assert_eq!(rect.area(), 10000);
    }
} 

But this looks like little bit hacky I think.

Not answering the original question, but still...

In Rust, it's usually better to define really small traits. I would even say than majority of the traits are one or two methods long. So, to better reuse code, you might start with splitting your trait:

trait HasColor {
    fn color(&self)
}
trait Movable {
    fn move(&self, x: i32, y: i32) -> Self;
}
trait Shape {
    fn area(&self) -> i32;
    fn has_corner(&self) -> bool;
}

Answering your original question is a bit hard, because to understand how to structure code, you need to know not only abstractly how domain objects look like, but also how they are used in this particular case. You can arrive at wildly different implementations for the same thing: for example, an entity component system might be the best fit ( Component · Decoupling Patterns · Game Programming Patterns ).

2 Likes

How do you go about managing your types then? I find that when I define small traits, I end up with interfaces down the line that take types like fn a<K>(&self, k: K) where K: HasColor + Sync + Send + Display + Clone + Moveable + Shape { ... }

I guess we can define types like type ColouredShape = Moveable + Shape + HasColor; and use that everywhere. Did we gain anything by breaking up the trait?

1 Like

You can't do type ColouredShape = Moveable + Shape + HasColor;, but you can do trait ColouredShape: Moveable + Shape + HasColor {}. By breaking the trait you gain the ability to write fn a<K: HasColor>(&self, k: K) and to easily combine implementations out of different pieces:

struct GenericShape {
    color: Box<HasColor>,
    position: Box<Moveable>,
    shape: Box<Shape>
}
2 Likes