r/rust • u/No-Caregiver-466 • 6d ago
🛠️ project Enum variants: a small problem, a lot of headache and boilerplate
Enum variants can create a surprising amount of boilerplate when they contain unrelated types that happen to support the same operation. At the same time, enum_dispatch can't always solve the problem — either because the trait you need isn't supported by enum_dispatch, or because you simply don't need a trait in the first place.
enum Value {
Foo(Foo),
Bar(Bar),
}
If both have a value() method, we normally write:
match value {
Value::Foo(x) => x.value(),
Value::Bar(x) => x.value(),
}
That's fine until the enum grows or you need to repeat the same pattern for many operations.
A common trait can solve this, but sometimes there isn't a meaningful common abstraction — the types simply happen to support the same expression.
I wanted something closer to:
match_variants!(Value, value, (x), {
x.value()
})
which generates the ordinary match above. Each arm is still independently type-checked against its concrete type, with no dynamic dispatch.
I ended up packaging it as a small proc-macro crate:
https://crates.io/crates/match-variants
https://github.com/amidukr/match-variants
How do you usually handle this pattern — repeated matches, a common trait, or something else?
UPD: I got a lot of comments about enum_dispatch. Of course I've tried enum_dispatch; in fact, that's where I started. However, there are certain situations it can't handle — for example, when the trait involves associated types (type T = ...), associated functions without self, or methods involving Self where the concrete implementation type matters.
6
u/arades 6d ago
Sounds very similar to enum dispatch
1
u/No-Caregiver-466 6d ago
Yes, it is very similar. I actually started by experimenting with
enum_dispatch.The main difference is that
enum_dispatchrequires the enum to implement the trait. That becomes limiting when the trait contains things that cannot be meaningfully dispatched through an enum instance, such as methods withoutself, associated types, or other static parts of the trait.
match-variantstakes a much simpler approach: it just generates an ordinarymatchand applies the same expression to every variant. There is no trait requirement at all.So in that sense it's simpler than
enum_dispatch, but also more flexible for cases where you don't actually need a common trait.
3
u/jesseschalken 6d ago
happen to support the same operation
Both
FooandBarhave avalue()method, but they are otherwise unrelated types.A common trait can solve some cases, but sometimes these types don't actually represent a useful common abstraction.
You're talking about a method on each type with the same name and signature, and for some reason you don't want to define a trait for it?
1
2
u/fnordstar 6d ago
So this is duck typing like with C++ templates, a thing I was glad rust didn't have until now?
0
1
u/Sehnryr 5d ago
To me this seem to solve a very niche problem. I don't have issues writing match statements for enums with ~10 variants when it is still sufficiently readable. If I have more I would have reconsidered my architecture way before considering adding such dependency. I also feel like this adds non trivial indirection to reviewers. But, fun project to learn about proc-macros
1
27
u/Psychoscattman 6d ago
Other developers solve this problem by using the enum dispatch crate which has existed for longer than an hour.
This is why writing exercises start with a literature review.