21

Using the enum Axes to confine Coordinate and Quaternion:

#[derive(Clone)]
pub enum Axes {
    Coordinate {
        x: f64,
        y: f64,
        z: f64,
        reserve: Vec<f64>,
    },
    Quaternion {
        x: f64,
        y: f64,
        z: f64,
    },
}

impl Axes {
    pub fn shift(&mut self, Sample: &Axes) -> () {
        let Dup: Axes = self.clone();
        match Dup {
            Axes::Coordinate { x, y, z, reserve } => match &Sample {
                Axes::Coordinate { x, y, z, reserve } => {
                    *self = Axes::Coordinate {
                        x: *x,
                        y: *y,
                        z: *z,
                        reserve: reserve.to_vec(),
                    };
                }
                _ => panic!(),
            },
            Axes::Quaternion { x, y, z } => match &Sample {
                Axes::Quaternion { x, y, z } => {
                    *self = Axes::Quaternion {
                        x: *x,
                        y: *y,
                        z: *z,
                    };
                }
                _ => panic!(),
            },
        }
    }
}

Using the trait Axes to link the Coordinate and Quaternion structs:

pub trait Axes {
    fn shift(&mut self, Sample: &Axes) -> ();
    fn fold(&mut self, Sample: &Axes) -> ();
}

pub struct Coordinate {
    pub x: f64,
    pub y: f64,
    pub z: f64,
    pub reserve: Vec<f64>,
}

pub struct Quaternion {
    pub x: f64,
    pub y: f64,
    pub z: f64,
}

impl Axes for Coordinate {
    fn shift(&mut self, Sample: &Axes) -> () {}
    fn fold(&mut self, Sample: &Axes) -> () {}
}

impl Axes for Quaternion {
    fn shift(&mut self, Sample: &Axes) -> () {}
    fn fold(&mut self, Sample: &Axes) -> () {}
}

Is a trait implemented on structs more accessible and more efficient in this case? I am confused of which to use and in what cases.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
Chaffon Liu
  • 323
  • 4
  • 7
  • Unrelated to your question, but in Rust, functions, fields and variables are almost always lowercase. Other programmers might be confused reading your code if you capitalize things this way. Of course, that's only a problem if you plan to have other programmers read your code. :) – Kwarrtz Sep 09 '18 at 07:42
  • Find interesting points about this topic [there](https://users.rust-lang.org/t/performance-implications-of-box-trait-vs-enum-delegation/11957/4) :) – iago-lito Jun 08 '19 at 07:42

2 Answers2

18

One of the big differences between using traits and enums for your situation is their extensibility. If you make Axes an enum, then the two options are hardcoded into the type. If you want to add some third form of axis, you'll have to modify the type itself, which will probably involve a lot of modifications to the code with uses Axes (e.g. anywhere you match on an Axes will probably need to be changed). On the other hand, if you make Axes a trait, you can add other types of axes by just defining a new type and writing an appropriate implementation, without modifying existing code at all. This could even be done from outside of the library, e.g. by a user.

The other important thing to consider is how much access you need to the internals of the structs. With an enum, you get full access to all the data stored within the struct. If you want to write a function which can operate on both Coordinate and Quaternion using a trait, then the only operations you will be able to perform are those described in the Axes trait (in this case Shift and Fold). For instance, giving the implementation of Axes you gave, there would be no way for you to simply retrieve the (X,Y,Z) tuple via the Axes interface. If you needed to do that at some point, you would have to add a new method.

Without knowing more about how you plan to use these types it's difficult to say for sure which of these options is the better choice, but if it were me I would probably use an enum. Ultimately, it comes down largely to preference, but hopefully this will give you some idea of the sorts of things to be thinking about when making your decision.

Kwarrtz
  • 2,693
  • 15
  • 24
  • As @Kwarrtz said, firstly, trait will be more flexable, and doesn't break Open-Close rule; Secondly, trait's dispatch is automated while enumerate is handled by yourself. – superK May 04 '21 at 14:48
17

Another difference not mentioned in @Kwarrtz's answer is memory related.

  • enums can be stored directly on the stack, while a boxed trait will always require the heap. That is, enums are cheap to create, but boxed traits are not.
  • an enum instance will always be as big as its biggest variant (plus a discriminant in most cases), even if you store mostly small variants. This would be a problem in a case like this:

    enum Foo {
        SmallVariant(bool),
        BigVariant([u64; 100]),
    }
    

    If you were to store N instances of this type in an vector, the vector would always need N*(100*sizeof::<u64> + sizeOfDiscriminant) bytes of memory, even when the vector only contains SmallVariants.

    If you were using a boxed trait, the vector would use N * sizeOfFatPointer == N * 2 * sizeof::<usize>.

mcarton
  • 27,633
  • 5
  • 85
  • 95
  • 5
    Just for completeness, if you box just the array inside `BigVariant`, the vector ends up the same size as the boxed trait version, but you still get the other advantages of `enum` aside from stack allocation. – trent Sep 25 '18 at 15:02
  • If a enum store in the stack, what happened if I put something like String? – mamcx Sep 28 '18 at 14:28
  • @mamcx the `String` itself always has to allocate its internal buffer on the heap, but putting this into an `enum` won't box it any further, while a `Box` will have to indirections. – mcarton Sep 28 '18 at 14:48