How to eliminate boilterplate pattern matching with same branches?

I'm talking about things like

impl T {
    fn foobar(&self) {
        match self {
            T::A(inner) => bar(inner.foo()),
            T::B(inner) => bar(inner.foo()),
        }
    }
}

I found these solutions:

For simple cases, enum_dispatch can work. But it has some limitations, e.g., not work with associate types, not IDE friendly. Another point is that it is required to use a trait for the inner types.

match_any and match_template seems powerful enough, but require user to provide all enum variants (as opposed to enum_dispatch).

I'm wondering is it possible (and reasonable) to auto generate an exhaustive pattern matching with arbitrary template code? Anyway, what's the best practice for such cases?

You can add a method get_foo to the enum so that you don't need to pattern match at all.

But get_foo itself needs to pattern match. Actually I'm just talking about maintaining such kind of wrapper methods.

Maybe this topic is what you are looking for? Auto generate .as_XYZ for enum - #3 by moy2010

This post seems not relevant?

Sorry, it's possibly a bug in Discord. I was typing from my mobile phone and it consistently was copying the wrong url. I have fixed it now.

Thanks for your help. But these are not what I'm looking for. They are useful to convert an enum into one specific variant, but I'm looking for something like a (type unsafe) map on an enum with heterogeneous variants.

... A general version of either::for_both

No, it's not possible. This is a gap in Rust's type system. Enum variants aren't types, and generics requires types, so you will have to use a match to bridge that gap every time.

In principle this could be solved without any additions to the type system per se. This could be valid code:

impl T {
    fn foobar(&self) {
        match self {
            T::*(inner) => bar(inner.foo()),
        }
    }
}

where the * is expanded to all variants in the enum declaration. (Or even: all variants with matching field count/names.) This information is already present at this site for exhaustiveness checking.

But macros can to some extend bypass the type system. I'd like to see some deeper reasons if this cannot be achieved by macros.

Because macros are run before any types are known. That's because macros are capable of making code that defines or changes types, so Rust can't begin to figure out the types until all macros are finished generating code. Making macros type-aware would create a circular dependency in Rust's compilation model.

At the point when macro runs something like T is just the ASCII letter T, not an instance of a generic type. You can't even know if it's an enum (the enum_dispatch crate does hacky things to estimate that, and can't do it properly in all cases). Doing this properly may not even be possible, because fields of the enum may be generated by another macro, or in generic code thanks to type inference and recursion the exact types may depend on result of the function your macro is currently generating (so your macro's input depends on your macro's output).

1 Like

If it's different than the existing same-type match self { T::A(inner) | T::B(inner) }, then the bar(inner) can't exist in the AST, because there is no way to express the superposition of the many types of inner.

That might be implemented as kind of a late-expansion macro that syntactically does copy'n'paste of the match arm for every variant. That could work, but it seems super narrow for one case.

Thanks for your explanation!

Just came up with an idea: use an attr macro to generate another macro for_all_xxx_variants, where the variants are passed to match_template :thinking:

1 Like

Writing a fn inner(&self) -> &Inner helper method is sufficient most of time (for me).

It doesn't work when each variant has similar but different types.

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.