15

Given these two structs:

pub struct RectangleRenderer {
    canvas: Canvas,
    origin: Point,
    shape: Rectangle,
}

pub struct CircleRenderer {
    canvas: Canvas,
    center: Point,
    shape: Circle,
}

As I come from Java, I would extract a base class ShapeRenderer out of those and apply the fields canvas and origin into that while the specific types will keep their field called shape. What's the best practice in Rust for this situation since traits only act similar to interfaces and therefore do not allow properties/fields?

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
xetra11
  • 7,671
  • 14
  • 84
  • 159
  • I found a similar question where an answer talks about composition -> http://stackoverflow.com/questions/32736170/what-is-the-best-way-to-inherit-a-struct-in-rust-1-3 . – xetra11 Sep 10 '16 at 18:24
  • 2
    Alternatively we could have a `struct Renderer`. – kennytm Sep 10 '16 at 18:40
  • @kennytm I wouldn't say that's "alternative" - that *is* composition! – Shepmaster Sep 10 '16 at 18:46
  • @xetra11 Why not just have an `enum Shape` and a `ShapeRenderer` with `shape: Shape`? – ljedrz Sep 10 '16 at 18:56
  • 2
    @ljedrz enums come with runtime overhead, generics do not. I'm not sure if that matters much here, but (without knowing more), this looks to me a perfect place for generics. – paholg Sep 10 '16 at 19:26
  • @kennytm how does your example exactly work? Would `Shape` be a struct which holds the `canvas` and `origin` fields and rectangle actually the `shape` field? And declaring `struct Rectangle` would bring in the properties of `Shape` similar to Java's `Rectangle extends Shape` ? I would try it out myself but am not on my dev machine atm. – xetra11 Sep 10 '16 at 22:24
  • Another question suggests composition https://stackoverflow.com/questions/68756315/how-to-program-shared-behaviors-in-rust-without-repeating-same-code-in-each-modu – Michael Freidgeim Oct 23 '21 at 23:47

1 Answers1

16

This looks like a perfect case for generics.

You can make a single struct like this:

struct ShapeRenderer<T: Shape> {
    canvas: Canvas,
    origin: Point,
    shape: T,
}

Note that I have bounded the generic type T by a trait Shape (that you would have to create). You can put any bounds here you like (or no bounds at all), but you will be restricted to using members of those traits.

Anything that you want to be able to access in your shapes would need to be exposed by Shape. For example, if you need the center and area, then the trait definition might look like this:

trait Shape {
    fn center(&self) -> (f64, f64);
    fn area(&self) -> f64;
}

If that is not enough flexibility, you could also give ShapeRenderer special behavior for only specific shapes. For example:

impl ShapeRenderer<Rectangle> {
    fn n_sides(&self) -> u32 {
        4
    }
}

Note that inside this impl, we have access to all the fields of Rectangle, not just the functions in Shape.


Alternatively, you could create a base struct and then include it as a member of your final structs:

struct Renderer {
    canvas: Canvas,
    origin: Point,
}

struct CircleRenderer {
    renderer: Renderer,
    shape: Circle,
}

struct RectangleRenderer {
    renderer: Renderer,
    shape: Rectangle,
}

This is the closest thing in Rust to standard inheritance.


Thirdly, if you only care about code duplication when creating these structs and don't want them to share anything but fields, you could use a macro:

macro_rules! make_renderer {
    ($name: ty, $shape: ty) => (
        struct $name {
            canvas: Canvas,
            origin: Point,
            shape: $shape,
        }
    );
}

make_renderer!(CircleRenderer, Circle);
make_renderer!(RectangleRenderer, Rectangle);

While the generics example is the most complicated, it is also the most powerful and flexible. It lets you easily have code shared between your structs, while also letting you have code specific to one that gives you access to all its fields.

paholg
  • 1,910
  • 17
  • 19
  • 1
    Actually I was more looking for something that only decrease the code duplication for the fields itself. Like having a struct `ShapeRenderer` which holds the `canvas` and the `origin` field and then one concrete struct called i.e. `RectangleRenderer` which holds the `shape` field but also *inherits* the fields of `ShapeRenderer` - similar to Java's inheritance `RectangleRenderer extends ShapeRenderer`. Traits only give me the option to abstract functions/methods but not fields. – xetra11 Sep 10 '16 at 22:33
  • @xetra11 you can use macros to reduce code duplication too https://users.rust-lang.org/t/best-way-to-reuse-partial-trait-implementation/6840/1 – breeden Sep 11 '16 at 01:46
  • @xetra11 This does reduce duplication. I think you are just used to Java-style inheritance, but it's a matter of preference; personally I'm very happy Rust doesn't have it. – ljedrz Sep 11 '16 at 06:25
  • Hm okay maybe I'm really way too used to Java. So there is no other way to avoid duplication on non-method structs? – xetra11 Sep 11 '16 at 07:42
  • 1
    @xetra11 I have added two other methods to the answer. – paholg Sep 11 '16 at 18:51
  • You could add in a link to the definition of "Composition," as that's what your second example is showing. – JosephTLyons Jan 16 '22 at 21:06