Phil Lord
Phil Lord

Reputation: 3057

Is there any way to match the body of a match statement with a Rust macro?

I have something like this in mind:

macro_rules! mymatch {
   ($obj:ident, ($matcher:tt => $result:tt),*) => {
       match $obj {
           $matcher
       },
   }
}

mymatch! {
    x,
    10 => "Ten",
    n if n < 5 => "Less than 5"
}

I don't want to reimplement the match statement, but I do want to put additional stuff on either side. However, I can't work out how to match the contents of a match statement.

Upvotes: 5

Views: 5551

Answers (1)

Peter Hall
Peter Hall

Reputation: 58715

There a few things wrong with your syntax. If you want to match a repeated pattern then you need to start the grouping with a $, like this: $($matcher:tt => $result:tt),*.

Next, you'll likely want to match more than identifiers, since any expression is valid to match against, so change that to expr. Left of the => you will want a pat (pattern), not a tt. A token tree is general, but for example, in n if n < 5 will match 5 separate tokens - n, if, n, <, 5 - and you generally only fall back on that when nothing else works. The same for result - this should be an expr (expression) too.

Ignoring the if < 5 guard for now, you can match the following:

my_match! {
    x,
    10 => "Ten",
    _ => "something else"
}

with the following macro:

macro_rules! my_match {
   ($obj:expr, $($matcher:pat => $result:expr),*) => {
       match $obj {
           $($matcher => $result),*
       }
   }
}

The guards are annoying because they are optional, but the ? quantifier is not yet stable. Instead you have to use * (0 or more), even though it will technically match more than you need it to.

The full macro is then:

macro_rules! my_match {
   ($obj:expr, $($matcher:pat $(if $pred:expr)* => $result:expr),*) => {
       match $obj {
           $($matcher $(if $pred)* => $result),*
       }
   }
}

And supports the this usage:

let x = 7;
let s = my_match! {
    x,
    10 => "Ten",
    n if x < 5 => "Less than 5",
    _ => "something else"
};
println!("s = {:?}", s); // "Something else" 

A block is also an expr, so this is also valid:

my_match! { 
    x,
    10 => "Ten",
    n if x < 5 => { 
        println!("it was {}", n); 
        "Less than 5" 
    },
    _ => "something else"
};

Upvotes: 6

Related Questions