Search code examples
rustrust-macros

Passing & and &mut through macro matched types


I've boiled down an issue I'm seeing into this snippet:

macro_rules! test_impl {
    (&mut $_:ty) => { 1 };
    (&$_:ty) => { 2 };
    ($_:ty) => { 3 };
}

macro_rules! test {
    ($val: literal, $($t:ty), *) => { ($val $(, test_impl!($t))*) }
}

fn main() {
    // I'm expecting (0, 3, 2, 1) here...
    println!("{:?}", test!(0, f64, &f64, &mut f64));
}

When ends up printing out:

(0, 3, 3, 3)

It seems like the reference and mutable parts of the t type aren't getting passed through. Am I understanding how this works wrong? Is there a way to pass the "reference/mut-ness" through the outer test! macro and match it in test_impl!?


Solution

  • Yes. Quoting the reference:

    When forwarding a matched fragment to another macro-by-example, matchers in the second macro will see an opaque AST of the fragment type. The second macro can't use literal tokens to match the fragments in the matcher, only a fragment specifier of the same type. The ident, lifetime, and tt fragment types are an exception, and can be matched by literal tokens.

    You cannot work around that with declarative macros: proc macros are able to remove those invisible fragments (they're represented as groups with invisible delimiters), but macro_rules! cannot. The only way is to not capture them from the beginning and match them with tt. Matching complex fragments, like ty, can be hard and require tt munching.