Macro_rules! generated match pattern does not work, but the same code does work

This code does not compile with errors. But if I replace the path! macro with what it exactly expands to then it perfectly works. I tried with the simpler macro and it worked.

error[E0425]: cannot find value `s` in this scope
  --> src/main.rs:31:27
   |
31 |         path!(int 10, str s) => {
   |                           ^ help: a local variable with a similar name exists: `p`

error[E0425]: cannot find value `s` in this scope
  --> src/main.rs:32:28
   |
32 |             println!("yes {s:?}");
   |                            ^ help: a local variable with a similar name exists: `p`

error: arbitrary expressions aren't allowed in patterns
  --> src/main.rs:31:27
   |
31 |         path!(int 10, str s) => {
   |                           ^

playground

#[derive(Debug, Clone, PartialEq, Eq, Hash)]
enum PathElement {
    Int(i32),
    String(&'static str),
}

macro_rules! path_element {
    (str $element:expr) => {
        PathElement::String($element)
    };
    (int $element:expr) => {
        PathElement::Int($element)
    };
}

macro_rules! path {
    ($($typ:ident $element:tt), +) => (
        [
			$(
				path_element!($typ $element),
			)+
		]
	);
}

fn main() {
    let p = path!(int 10, str "hello");
    println!("{:?}", p);
    match p {
        //[PathElement::Int(10), PathElement::String(s)] => {
        path!(int 10, str s) => {
            println!("yes {s:?}");
        }
        _ => {
            println!("no");
        }
    }
}

replace (str $element:expr) with (str $element:tt) solved the problem. It took me more than an hour...

1 Like

The playground isn't working for me, but I expect the problem stems from your use of an expr matcher. From my understanding, this transforms $element into an invisibly-delimited group that is only legal as part of an expression, and not a pattern. (Think ($element) with a non-printing character for the parentheses).

I think the primary motivation here is to prevent odd order-of-operations issues, so that 1 + 2 getting replaced into $expr * 3 evaluates to 9 instead of 7.

3 Likes

Yes, I've just figured this out by myself.

ident should work too because tt and ident are passed as they are (while other metavariables may not).

It's unsurprising that it'll take some time to learn some deep edge cases on Rust macros like this. Take easy.

3 Likes