Could enums be considered an anti-pattern?

I agree that this is the biggest difference between enums and traits, but I'd add that there are some cases where (performance questions aside), it may make more sense to represent a closed set as a trait.

The other main difference between an enum and a trait is that an enum can be destructured into its variants, by anyone who has access to the enum. This means an enum never encapsulates its variance; anyone who gets one can always open it up to find out which specific variant you gave to it.

In my code (and I think for many people this is true), my types are usually divided into two broad categories:

  • "Plain old data" types - usually these implement Copy, usually they are shortlived, they tend to get passed around as messages between types in the second category.
  • "Object" types - usually these own some heap-allocated data, usually they live for a long time, they tend to be the things receiving messages from one another.

I try to do a few things differently between these two categories of types. Data types I try to keep immutable, and write methods on them as functions which produce new values, rather than changing them. Object types I let keep mutable state, and therefore I try to encapsulate them as much as possible.

So if I have a thing that wants to be an object type and an enum, the lack of encapsulation is sort of a problem.

There are two solutions to this. The first is to use a trait, and trait objects. This has some downsides: its almost always going to be at least a little slower, and you have to deal with 'object safety' issues. The second is to wrap your enum in a struct:

pub struct Foo {
    inner: InnerFoo,
}

impl Foo {
     pub fn bar(&self) {
        self.inner.bar();
    }
}

enum InnerFoo {
    //...
}

impl InnerFoo {
    fn bar(&self) {
        match *self {
            //...
        }
    }
}

The downside of this approach is that you have a lot of boilerplate / rightward drift compared to just defining a bunch of types and implementing the trait for them.

We've thought about a 'third way' approach of introducing closed or sealed traits, which would have a more enum-like representation rather than trait objects, but still be implemented as traits to make it easier to create them.

1 Like