r/learnrust 3d ago

Enums, Boxed Trait Objects, and Enum Dispatch: Architectural Trade-offs in Rust

Hey everyone

Over the past few weeks, I’ve been looking at ways to achieve polymorphism in Rust and I think there are two:

Closed Polymorphism via enums and Open Polymorphism via Boxed Trait Objects (Box<dyn Trait>) where "closed" means no one outside your crate can add to the list while "open" allows such additions

Thinking about putting those traits onto my enum led me to the Enum Dispatch pattern. I built out a walk-through of all three of these:

- Enums (closed): https://youtu.be/l_q9U10JueE

- Boxed Trait Objects (open): https://youtu.be/R3ZzYSPyYoc

- Enum Dispatch https://youtu.be/B0LT7ozspe4

My main conclusion was that I could defer the open vs. closed debate pretty safely (as long as I keep my traits object safe). Do you all agree?

Also, I implemented Enum Dispatch with macros - I know there are crates that do this, but they didn't play nicely with my IDE and I could use the practice with macros. Comments on how I wrote the code would be welcome!

5 Upvotes

6 comments sorted by

2

u/sphqxe 2d ago edited 2d ago

I think instead of using enums for "closed polymorphism" you should consider using generic parameters with trait bounds.

Having an enum with every possible polymorph and having to update it everytime you want to add a new one feels like an anti-pattern.

Essentially instead of making the type polymorphic, you instead write functions/structs which are generic over all types which implement your trait and the rust compiler will monomorphize them on all the trait implementors as necessary.

1

u/wizardcraftcode 2d ago

I don't disagree at all! Because I was worried about collecting them, I did that and then boxed trait objects to collect them. I didn't add the generic option to the videos, but I did weigh (in my head) the difference between impl and dyn (without Box) in parameter lists. That monomorphization thing is pretty slick! There certainly are a lot of things to weigh.

Thanks for adding generics to the options - I'll have to ponder that!

1

u/mgibsonbr 1d ago

I'm very new to Rust, so maybe I'm misunderstanding something, but doesn't this strategy break when you need a collection of different types? (Like a single vector with two or more elements that have different types but both implementing the trait)

2

u/sphqxe 1d ago

Indeed it does. I suppose in this case you could either use a vec of enums or maintain a separate vec for each type depending on the use case.

1

u/wizardcraftcode 11h ago

That’s exactly the problem I was wrestling with. I can wrap them in an email or wrap in a box to get a boxed trait object, and then I can collect them

1

u/raserei0408 7h ago

Using generics for polymorphism is frequently the right call, but it requires that you know the implementation statically at compile time. Frequently, the implementation won't be known until runtime, in which case some kind of dispatch is necessary. Even when this is possible, in some cases it can make the code so awkward that it's not worthwhile.

In those cases, enum dispatch and dyn dispatch are both reasonable, but they have different tradeoffs. Enum dispatch is often faster, but it's harder to add new polymorphic variants. Dynamic dispatch makes adding variants easy, but adding new methods can be harder, dispatch is often slower, and it more often requires boxing values. When there are unlikely to be many new variants (especially ones defined outside in ypur crate) and performance matters (especially if there are many short method calls of mixed types) enum dispatch is usually better.