Schottky
Schottky

Reputation: 2024

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.

Upvotes: 0

Views: 47

Answers (1)

PitaJ
PitaJ

Reputation: 15094

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

Upvotes: 2

Related Questions