在声明性宏中构建所有元素对(二次集)

Luk*_*odt 4 rust rust-macros rust-decl-macros

我有一个标识符列表,我想为该列表中的每对标识符调用一个宏.例如,如果我有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);
Run Code Online (Sandbox Code Playgroud)

当然,这是一个虚拟的例子.在我的真实代码中,标识符是类型,我想生成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);
Run Code Online (Sandbox Code Playgroud)

但是,这会导致此错误:

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 | |             )*
  | |_____________^
Run Code Online (Sandbox Code Playgroud)

我觉得它有点意义,所以我尝试了别的东西(Playground):

macro_rules! print_all_pairs {
    ($($x:ident)*) => {
        print_all_pairs!(@inner $($x)*; $($x)*);
    };
    (@inner $($x:ident)*; $($y:ident)*) => {
        $(
            $(
                println!("{} <-> {}", $x, $y);
            )*
        )*
    };
}
Run Code Online (Sandbox Code Playgroud)

但这导致与上面相同的错误!

是否可以使用声明性宏?

Luk*_*odt 5

是否可以使用声明性宏?

是的.

但是(据我所知)我们必须通过头/尾递归迭代一次,而不是在$( ... )*任何地方使用内置机制.这意味着列表长度受宏扩展的递归深度限制.但是,对于"仅"12项而言,这不是问题.

在下面的代码中,我通过将宏名称传递给宏来将"for all pairs"功能与打印代码分开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);
Run Code Online (Sandbox Code Playgroud)

此代码打印:

a <-> a
a <-> b
a <-> c
b <-> a
b <-> b
b <-> c
c <-> a
c <-> b
c <-> c
Run Code Online (Sandbox Code Playgroud)