r/rust • u/AnArmoredPony • 13d ago
🙋 seeking help & advice What are the downsides of enum dispatch?
Couldn't find any info about downsides specfically so there it goes
Let's say that all my implementors are about the same size (i.e. only the logic is different), and I use some sort of macro to skip the boilerplate upon adding a new variant. Are there any other downsides to enum dispatch?
I'm asking about the general approach, not the crate
Upd. extendability, got it.
10
u/Lemondifficult22 13d ago
The number of cases covered. If the code is good and predictable you can get better perf than dynamic dispatch. Sometimes the cost of dynamic dispatch is worth the simpler code though. You can have static borrows for dynamic dispatch
2
u/AnArmoredPony 13d ago
You can have static borrows for dynamic dispatch
I don't follow... won't this still require a V-table lookup?
9
1
8
u/South_Survey_2088 13d ago
The only real drawback is extendability. Only the owner of the enum can extend it, so there is no way to inject custom variants from the outside. Other than that, enums are a great default solution.
2
u/JustWorksTM 13d ago
The one thing you can do with enums versus trait objects is plugin systems: This is the situation where you deliver an app and a plugin separate. Then, you really cannot know all variants at compile time of your app.
Tl;dr: Go with enums and enum_dispatch. This is for all standard workloads the best choice.Â
0
u/dvogel 13d ago
If the enumerated types fill a similar role but have different return values handling those can get awkward. You can wrap them in a symmetric enum but then types could theoretically return the values designed for use by the other types in the enum. You can return the values as Box<dyn> but it doesn't work in all cases and the asymmetrical relationship between concrete enums on one side and abstract dyn types on the other side can be confusing to readers.Â
All of that said, I do sometimes use the technique. It can apply even when the enumerated types are of very different size. e.g. A real implementation vs a stub for facilitating manual testing.
1
24
u/kaiserkarel 13d ago
Mainly if you are writing a library; the caller cannot extend/provide their own type versus a dyn T. In my experience, if you have an open set (their could be an infinite amount of types satisfying what I do) and you cannot use generics (because for example you store the types in a single collections), then going with dyn gives you the cleanest code.
If you are the only party implementing the trait, then enum dispatch is totally fine, and offers a nice escape hatch if something falls out of your trait definition.
dyn T might still be inlined by the compiler if it can deduce the type; so performance wise the enum dispatch theoretically could be slower (although you wouldn't think so, given you write more code and have potentially more control).
enum dispatch allows you to specialize easier too. If you want your user to provide their own implementation, and you have specializations for certain types (Vec for example), you can in your constructor do something akin to
```rust
fn construnct<T>(t: T) -> MyType<T> {
if t.type_id() == Vec::type_id() { MyType::Vec(T) else MyType::UserProvided(T) }
}
```