5
votes

Types passed directly to macros pattern match the way you would expect, but if they're passed through another macro as ty, they stop matching:

macro_rules! mrtype {
    ( bool )   => ("b");
    ( i32 )    => ("i");
    ( f64 )    => ("f");
    ( &str )   => ("z");
    ( $_t:ty ) => ("o");
}

macro_rules! around {
    ( $t:ty ) => (mrtype!($t));
}

fn main() {
    println!("{}{}{}", mrtype!(i32), around!(i32), around!(&str));
}

This prints ioo instead of iiz.

Passing tts instead of tys works, but if you have &str you need 2 tts, making everything unnecessarily complicated.

1

1 Answers

7
votes

This doesn't work and cannot be made to work.

To summarise the Captures and Expansion Redux chapter of The Little Book of Rust Macros: the problem is that with the exception of tt and ident captures, macro_rules! is completely unable to destructure or examine captured tokens. Once you capture something as ty, it irrevocably becomes a black box to macro_rules!.

To put it another way: &str is not a type, as far as macro_rules! is concerned: it's two tokens, & and str. When you capture and then substitute &str as ty, though, it becomes a single "meta token": the type &str. The two are no longer the same, and as such don't match.

If you intend to later match against or destructure tokens, you must capture them as tts or idents (if possible). In this specific case, you could rewrite the rule for around to instead be ($($t:tt)*) => (mrtype!($($t)*));, which preserves the original token sequence.