Построить все пары элементов (квадратичное множество) в декларативном макросе
У меня есть список идентификаторов, и я хочу вызвать макрос для каждой пары идентификаторов из этого списка. Например, если у меня есть a
, b
а также c
Я хотел бы создать это:
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);
Конечно, это фиктивный пример. В моем реальном коде идентификаторы являются типами, и я хочу создать impl
блоки или что-то в этом роде.
Моя цель - перечислить каждый идентификатор только один раз. В моем реальном коде у меня около 12 идентификаторов, и я не хочу вручную записывать все пары 12² = 144. Поэтому я подумал, что макрос может мне помочь. Я знаю, что это можно решить с помощью всех мощных процедурных макросов, но я надеялся, что это также возможно с помощью декларативных макросов (macro_rules!
).
Я попробовал то, что мне показалось интуитивным способом справиться с этим (две вложенные "петли") ( 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);
Однако это приводит к этой ошибке:
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 | | )*
| |_____________^
Я думаю, это имеет смысл, поэтому я попробовал кое-что еще ( площадка):
macro_rules! print_all_pairs {
($($x:ident)*) => {
print_all_pairs!(@inner $($x)*; $($x)*);
};
(@inner $($x:ident)*; $($y:ident)*) => {
$(
$(
println!("{} <-> {}", $x, $y);
)*
)*
};
}
Но это приводит к той же ошибке, что и выше!
Возможно ли это вообще с декларативными макросами?
1 ответ
Возможно ли это вообще с декларативными макросами?
Да
Но (насколько мне известно) мы должны перебирать список через рекурсию head/tail один раз вместо использования встроенного $( ... )*
механизм везде. Это означает, что длина списка ограничена глубиной рекурсии макроса. Это не проблема для "только" 12 предметов.
В приведенном ниже коде я отделил функциональность "для всех пар" от печатного кода, передав имя макроса в for_all_pairs
макро. ( Детская площадка).
// 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);
Этот код печатает:
a <-> a
a <-> b
a <-> c
b <-> a
b <-> b
b <-> c
c <-> a
c <-> b
c <-> c