rustrust-macrosrust-decl-macros

Build all pairs of elements (quadratic set) in declarative macro


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?


Solution

  • 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