Search code examples
rustrust-macrosmacro-rules

Simultaneously allow expressions and patterns in rusts macro_rules


I would like to auto-generate the default case for repeating match statements like the following:

enum A {Foo, Bar, Foobar(B) }
enum B {Baz}

use A::*;
use B::*;

match x {
    Foo => { /*...*/ },
    Bar => { /*...*/ },
    Foobar(Baz) => { /*...*/ }
    _ => consume([Foo, Bar, Foobar(Baz)])
}

All three elements are both valid as patterns as well as expressions in the given context. I have, therefore, tried the following macro rule:

macro_rules! match_default {
    ($x:expr, $($pattern:pat_param => $action:block),+) => {
        match $x {
            $($pattern => $action),+
            _ => consume([$($pattern),*])
        }
    };
}

which gives me the error

error: expected expression, found pattern Foo

On the other hand, when I replace pat_param with expr, the error is

error: arbitrary expressions aren't allowed in patterns

Is what I am trying to achieve not possible using rusts macro_rules! system? I am OK with spelling out the actual type inside the macro ($x it will always be a variant of enum A), but so far I have had no success.


Solution

  • The solution is to pass the original input twice into an internal macro. This allows us to treat it once as patterns and the second time as expressions.

    macro_rules! match_default {
        ($x:expr, $($body:tt)*) => {
            //                      once for patterns, again for exprs
            match_default!(@inner $x, [[ $($body)* ]], [[ $($body)* ]])
        };
        (@inner $x:expr, [[ $($pattern:pat_param => $action:block),+ ]], [[ $($pattern_expr:expr => $_action_expr:block),+ ]]) => {
            match $x {
                $($pattern => $action),+
                _ => consume([$($pattern_expr),*])
            }
        };
    }
    

    playground