Can someone please explain to me why (some of) the following macros behave differently?
macro_rules! maybe_skip {
($prim:ty, $text:literal) => {
maybe_skip!($prim);
};
(u64) => {
println!("Skipping u64");
};
($prim:ty) => {
println!("Not skipping {}", stringify!($prim));
};
}
macro_rules! maybe_skip_again {
($prim:ident, $text:literal) => {
maybe_skip_again!($prim);
};
(u64) => {
println!("Skipping u64");
};
($prim:ident) => {
println!("Not skipping {}", stringify!($prim));
};
}
macro_rules! maybe_skip_again_again {
($prim:ident, $text:literal) => {
maybe_skip_again_again!($prim);
};
(u64) => {
println!("Skipping u64");
};
($prim:ty) => {
println!("Not skipping {}", stringify!($prim));
};
}
fn main() {
maybe_skip!(u32, "bla");
maybe_skip!(u64, "bla");
maybe_skip_again!(u32, "bla");
maybe_skip_again!(u64, "bla");
maybe_skip_again_again!(u32, "bla");
maybe_skip_again_again!(u64, "bla");
}
Output is:
Not skipping u32
Not skipping u64
Not skipping u32
Skipping u64
Not skipping u32
Skipping u64
the first one, maybe_skip!(u64, "bla")
, it matches the rust matcher arm, which captures the first token tree u64
as a type, then forward it to the next matcher arm.
because the meta variable is coerced into a type
token tree, it is forwarded to the next arm as a parsed opaque type
ast node, but the u64
matcher will only match the literal identifier u64
.
the key to understand this is, when a matcher matches the input tokens, the tokens captured by meta variables are partially parsed according to the meta variable kind specifier, and this "parsing" process cannot be "undone".
quoting from Macros By Example - The Rust 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.
3 Likes
Is there a way to make the first one work, so to match u64 as a type token?
If by the "first one" you mean your first maybe_skip
macro, it's already matching u64
as a type ($prim:ty
) token. If you want it to "force" match the second (u64)
regardless, use :ident
.
macro_rules! maybe_skip {
($prim:ident, $text:literal) => {
maybe_skip!($prim);
};
(u64) => {
println!("Skipping u64");
};
($prim:ty) => {
println!("Not skipping {}", stringify!($prim));
};
}
fn main() {
maybe_skip!(u32, "bla"); // Not skipping u32
maybe_skip!(u64, "bla"); // Skipping u64
}
I meant, is there a way to construct the arm inside the macro as something like this:
(u64 as type) => {...}
so you can keep $prim:ty
.
There isn't and there can't be. Not for this specific use case:
// a perfectly legal statement
let u64: u64 = 64_u64;
// now, if your macro_rules! only says:
macro_rules! maybe_skip {
(u64, $text:literal) => { ... }
}
// how is the compiler meant to know
// whether you are matching against `u64`
// as an identifier or a type here?
maybe_skip!(u64, "bla");
If you absolutely must match against the u64
type, use std::any::TypeId
.
There is a trick with nested macros to use particular tokens in the definition of another macro, but even that doesn't seem to fix the issue Rust Playground
Maybe with a proc macro you could remove the hidden group that wraps the u64
token (which is what makes it a "type" and not match the u64
token anymore), but that probably starts becoming pretty complex and slower.
Note that the user may just want to match on the token "u64" but still allow any syntactic type as input for which ident
is too restricting).