Match-variants 0.1.0 — Apply the same expression to heterogeneous enum variants without a common trait

I’ve published match-variants, a small procedural macro crate for applying the same expression to heterogeneous enum variants without requiring their payload types to implement a common trait.

The problem I wanted to solve is code like this:

match value {
    Value::Foo(x) => x.value(),
    Value::Bar(x) => x.value(),
}

where Foo and Bar are unrelated concrete types, but the same expression happens to be valid for both.

With match-variants:

#[derive(MatchVariants)]
enum Value {
    Foo(Foo),
    Bar(Bar),
}

let result = match_variants!(Value, value, (x), {
    x.value()
});

The macro expands to an ordinary Rust match, so every arm is independently type-checked against its concrete payload type.

There is no common trait requirement, trait-object dispatch, runtime type inspection, or allocation introduced by the crate.

It also supports struct-like variants and enums used across modules and external crates.

I originally built this while working with enum-based static dispatch and wanting to avoid introducing traits whose only purpose was to remove repetitive match arms.

Crate: crates.io: Rust Package Registry

GitHub: GitHub - amidukr/match-variants: Rust procedural macros for applying the same expression to heterogeneous enum variants without a common trait. · GitHub

This is the first 0.1.0 release, so API/design feedback is very welcome.

Wow, could we call it a structural typing in Rust?

I personally feel like traits is more natural approach for Rust, because when you implement one, you explicitly agree to obey a contract declared by it, but value function might just happen to share its name with another one.

There is another approach using traits with enum_dispatch.

However, when I started exploring it more deeply, I ran into several limitations. It works well for relatively straightforward traits, but becomes more restrictive once the trait contains things like associated types or functions without self.

And sometimes a trait simply isn't needed. :slight_smile:

I agree that a trait is preferable when there is a real contract that the types are supposed to implement. But sometimes two unrelated types just happen to support the same expression, and introducing a trait solely to eliminate repetitive match arms feels unnecessary.

That's the case I wanted match-variants to address.