Best practices to use pattern match with Enum?

I had Rust code like the following.

match my_enum {
    MyEnum::Variant1 => ...,
    MyEnum::Varinat2 => ...,
    _ => ...,
}

Later on, I decided that it makes more sense to add another variant MyEnum::Variant0 into MyEnum, which means that I have to update the code to be the following to keep the logic right.

match my_enum {
    MyEnum::Variant0 => ...,
    MyEnum::Variant1 => ...,
    MyEnum::Varinat2 => ...,
    _ => ...,
}

Unfortunately, such pattern matching has been used many times across the whole project and it is very tedious to go over all of them to fix the logic. One way which might have helped is to explicitly list all Enum variants instead of using _ to match all rest variants, i.e., something like below.

match my_enum {
    MyEnum::Variant1 => ...,
    MyEnum::Varinat2 => ...,
    MyEnum::Variant3 | MyEnum::Variant4 | MyEnum::Variant5 | MyEnum::Variant6 | MyEnumVariant7 | MyEnum::Variant8 | MyEnum::Variant9 => ..., 
}

The benefit of doing this explicit style is that if an Enum is updated later, the code won't compile forcing you to go over all use cases to fix issues. This is safer and more robust style. The downside is that the last branch becomes very verbose and it is even worse if the Enum has more variants.

My question is what is the best practice here?

1 Like

If you have an _ pattern, presumably there are a bunch of enum variants that you want to ignore or handle as the default case. In that case manually listing all the remaining variants would be tedious as you mention and an anti-pattern in my opinion.
Now, if you are adding another variant to the enum, you probably don't need to change every match, just the ones where it matters (and I am guessing it doesn't matter everywhere since you have the _ pattern).
If you find yourself changing every variant for a design change as an afterthought, well you have to bite the bullet - post-facto design changes are undoubtedly painful.
The Rust-Analyzer feature of "Find all references" may be of assistance to you in such a case.

1 Like

I feel it's basically as you've outlined: if there's a likelihood of another variant that needs non-fallback behavior, the explicit version is better than the catch-all, albeit tedious.

3 Likes

I doubt anyone would call it a best practice, but using a macro is an option here. Then if a new variant that should usually be treated the same as MyEnum::Variant3 and so on, then only the macro definition would need to be changed.

macro_rules! more_than_two {
    () => {
        MyEnum::Variant3 | MyEnum::Variant4 | MyEnum::Variant5 | MyEnum::Variant6 | MyEnum::Variant7 | MyEnum::Variant8 | MyEnum::Variant9
    }
}

match my_enum {
    MyEnum::Variant1 => ...,
    MyEnum::Variant2 => ...,
    more_than_two!() => ..., 
}

Playground Link

As a more serious suggestion, presumably if there are multiple places where the same patterns with the same group of several variants, then there's something about those variants that is the same. You could consider grouping them into a separate enum type like this:

enum MyMoreThanTwo {
    Variant3,
    Variant4,
    Variant5,
    Variant6,
    Variant7,
    Variant8,
    Variant9,
}

enum MyEnum {
    Variant1,
    Variant2,
    MoreThanTwo(MyMoreThanTwo),
}

This way a match like this would cause a compile error if a new variant is added to MyEnum, but not if a new one is added to MyMoreThanTwo:

match my_enum {
    MyEnum::Variant1 => ...,
    MyEnum::Variant2 => ...,
    MyEnum::MoreThanTwo(_) => ..., 
}
5 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.