I have a list of identifier and I want to invoke a macro for each pair of identifiers from that list. For example, if I have a
, b
and c
, I would like to generate this:
println!("{} <-> {}", a, a);
println!("{} <-> {}", a, b);
println!("{} <-> {}", a, c);
println!("{} <-> {}", b, a);
println!("{} <-> {}", b, b);
println!("{} <-> {}", b, c);
println!("{} <-> {}", c, a);
println!("{} <-> {}", c, b);
println!("{} <-> {}", c, c);
Of course, this is a dummy example. In my real code, the identifiers are types and I want to generate impl
blocks or something like that.
My goal is to list each identifier only once. In my real code, I have around 12 identifier and don't want to manually write down all 12² = 144 pairs. So I thought that a macro might help me. I know that it can be solved with the all powerful procedural macros, but I hoped that it's also possible with declarative macros (macro_rules!
).
I tried what I thought was the intuitive way to handle this (two nested "loops") (Playground):
macro_rules! print_all_pairs {
($($x:ident)*) => {
$(
$(
println!("{} <-> {}", $x, $x); // `$x, $x` feels awkward...
)*
)*
}
}
let a = 'a';
let b = 'b';
let c = 'c';
print_all_pairs!(a b c);
However, this results in this error:
error: attempted to repeat an expression containing no syntax variables matched as repeating at this depth
--> src/main.rs:4:14
|
4 | $(
| ______________^
5 | | println!("{} <-> {}", $x, $x);
6 | | )*
| |_____________^
I guess it makes kind of sense, so I tried something else (Playground):
macro_rules! print_all_pairs {
($($x:ident)*) => {
print_all_pairs!(@inner $($x)*; $($x)*);
};
(@inner $($x:ident)*; $($y:ident)*) => {
$(
$(
println!("{} <-> {}", $x, $y);
)*
)*
};
}
But this results in the same error as above!
Is this possible with declarative macros at all?
Is this possible with declarative macros at all?
Yes.
But (to the best of my knowledge) we have to iterate through the list via head/tail recursion once instead of using the built-in $( ... )*
mechanism everywhere. This means that the list length is limited by the recursion depth of macro expansion. That's not a problem for "only" 12 items, though.
In the code below, I separated the "for all pairs" functionality from the printing-code by passing a macro name to the for_all_pairs
macro. (Playground).
// The macro that expands into all pairs
macro_rules! for_all_pairs {
($mac:ident: $($x:ident)*) => {
// Duplicate the list
for_all_pairs!(@inner $mac: $($x)*; $($x)*);
};
// The end of iteration: we exhausted the list
(@inner $mac:ident: ; $($x:ident)*) => {};
// The head/tail recursion: pick the first element of the first list
// and recursively do it for the tail.
(@inner $mac:ident: $head:ident $($tail:ident)*; $($x:ident)*) => {
$(
$mac!($head $x);
)*
for_all_pairs!(@inner $mac: $($tail)*; $($x)*);
};
}
// What you actually want to do for each pair
macro_rules! print_pair {
($a:ident $b:ident) => {
println!("{} <-> {}", $a, $b);
}
}
// Test code
let a = 'a';
let b = 'b';
let c = 'c';
for_all_pairs!(print_pair: a b c);
This code prints:
a <-> a
a <-> b
a <-> c
b <-> a
b <-> b
b <-> c
c <-> a
c <-> b
c <-> c