How to process macro by pairs of expr

Hi!

I am learning how to write declarative macros, and have a question regarding the following problem:

  1. Take variable number of comma-separated expressions as input
  2. Print expressions by pairs, repeating until no pair-wise match is found
  3. Should also work for odd number of expr by printing the last argument at the end

Here's my naive attempt at solving the problem:

macro_rules! disp {
    ($($x:expr, $y:expr),*) => {{
        $(println!("{}, {}", $x, $y);)*
    }};
    ($($x:expr, $y:expr)*, $z:expr) => {{
        $(println!("{}, {}", $x, $y);)*
        println!("{}",$z);

        // Actual usecase is: pair!($(pair!($x, $y)),* , $z)
    }};
}

fn main() {
    disp!(1, 2, 3); // This works
    // disp!(1, 2, 3, 4, 5); // This doesn't work
}

For even number of inputs the first match arm works fine, but for odd number of inputs, the second match arm doesn't actually work as I intended, i.e. it doesn't match repeatedly if any odd number of inputs greater than 3 is used.

The reason I want to process the inputs by pairs instead of using a single expression is because I am calling another macro that only takes 2 inputs and performs some operation. It's not so easy to modify the said macro, but I want to make it work for more than 2 inputs with methods discussed above.

Any guidance would be appreciated! Thank you.

I think your second matcher doesn't work because it is using $($x:expr, $y:expr)*, $z:expr, which would match 1, 2 3, 4 5, where your matches are (x: 1 y: 2), (x: 3 y: 4), z: 5. Notice you haven't added a separating character before the * for x and y so disp!(, 3) would be valid.

If I was writing normal Rust and wanted to print pairs of items in a slice, I would use slice patterns and recursion like this:

fn disp_with_slice_patterms(items: &[u32]) {
    match items {
        [first, second, rest @ ..] => {
            println!("{}, {}", first, second);
            equivalent_disp2(rest);
        },
        [single] => println!("{}", single),
        [] => {},
    }
}

This approach translates almost directly to a declarative macro:

macro_rules! disp2 {
    (
        // Peel off the front two items
        $first:expr, $second:expr, 
        // Make sure we capture the rest in a "variable"
        $($rest:expr),* 
        // Ignore trailing commas
        $(,)?
    ) => {
        println!("{}, {}", $first, $second);
        // Recurse on the rest of the values
        disp2!($($rest),*);
    };
    // Handle the case when there is just one item left
    ($single:expr $(,)? ) => {
        println!("{}", $single);
    };
    // Do nothing when recursing with no items left
    () => {};
}

That general pattern of peeling items off the front and recursing on the rest is referred to as an Incremental TT Muncher in The Little Book of Rust Macros.

There is also a way to do callbacks with macros by passing in an ident:

macro_rules! for_each {
    ($callback:ident, [ $($item:expr),+] ) => {
        $(
            $callback!($item);
        )*
    }
}

macro_rules! print_it {
    ($item:expr) => {
        println!("{}", $item);
    }
}

fn main() {
    for_each!(print_it, [1, 2, 3, 4, 5, 6]);
}

Instead of recursion, since you’re building a list of statements individually, you can also do it with a bit of “preprocessing”

macro_rules! disp2 {
    ($($($e:expr),+$(,)?)?) => {
        disp3!{$($($e),+)?##}
    }
}
macro_rules! disp3 {
    ($($e1:tt $comma_or_hash:tt $e2_or_hash:tt),*$(##)?) => {
        $(disp4!{$e1 $comma_or_hash $e2_or_hash:tt})*
    }
}
macro_rules! disp4 {
    ($z:tt # #) => {
        println!("{}", $z);
    };
    ($x:tt, $y:tt) => {
        println!("{}, {}", $x, $y);
    };
}

fn main() {
    disp2!(1, 2, 3); // This works
    disp2!(1, 2, 3, 4); // This works
    disp2!(1, 2, 3, 4,); // This works
    disp2!(1, 2, 3, 4, 5); // This works
    disp2!(1, 2, 3, 4, 5,); // This works
}

This has the advantage (over recursion) of not hitting the recursion limit so easily.

Edit: Eh, damn it, it doesn’t work for even numbers anymore………

Edit2: Fixed.

Edit3: Here’s another solution (closer to the one I had originally) that works, too

macro_rules! disp2 {
    ($($($e:expr),+$(,)?)?) => {
        disp3!{$($([$e])+)?#}
    }
}
macro_rules! disp3 {
    ($([$e1:tt] $e2:tt)*$(#)?) => {
        $(disp4!{[$e1] $e2})*
    }
}
macro_rules! disp4 {
    ([$x:tt] [$y:tt]) => {
        println!("{}, {}", $x, $y);
    };
    ([$z:tt] #) => {
        println!("{}", $z);
    };
}
1 Like

Typical way to allow optional trailing comma only when the lists of arguments is non-empty
so this excludes disp2(,);

Packs up all the exprs into [] brackets, adds a trailing #

The trailing # can either be in $e2 or in the end $(#)?

Handles each case.


This approach trivially generalizes to triples, quadruples, etc…

macro_rules! disp_quintuples {
    ($($($e:expr),+$(,)?)?) => {
        disp_quintuples_helper1!{$($([$e])+)?####}
    }
}
macro_rules! disp_quintuples_helper1 {
    ($([$e1:tt] $e2:tt $e3:tt $e4:tt $e5:tt)*$(#)*) => {
        $(disp_quintuples_helper2!{[$e1] $e2 $e3 $e4 $e5})*
    }
}
macro_rules! disp_quintuples_helper2 {
    ([$a:tt] [$b:tt] [$c:tt] [$d:tt] [$e:tt]) => {
        println!("{}, {}, {}, {}, {}", $a, $b, $c, $d, $e);
    };
    ([$a:tt] [$b:tt] [$c:tt] [$d:tt] #) => {
        println!("{}, {}, {}, {}", $a, $b, $c, $d);
    };
    ([$a:tt] [$b:tt] [$c:tt] # #) => {
        println!("{}, {}, {}", $a, $b, $c);
    };
    ([$a:tt] [$b:tt] # # #) => {
        println!("{}, {}", $a, $b);
    };
    ([$a:tt] # # # #) => {
        println!("{}", $a);
    };
}

Hi @Michael-F-Bryan, thanks for the reply and links to great references! Starting from your example, I tweaked it slightly to make it work for all of my use cases:

macro_rules! join {
    (
        $first:expr, $second:expr
        $(,$rest:expr)*
    ) => {{
        let mut out = $first.to_string();
        let second = $second.to_string();
        out.push_str(&second);
        $(out.push_str(&join!($rest));)*
        out
    }};
    ($last:expr $(,)? ) => {{
        let out = $last.to_string();
        out
    }};
}

macro_rules! disp {
    (
        $first:expr, $second:expr
        $(,$rest:expr)*
    ) => {
        println!("{}, {}", $first, $second);
        disp!($($rest),*);
    };
    ($single:expr $(,)? ) => {
        println!("{}", $single);
    };
    () => {};
}

fn main() {
    disp!(1, 2); // outputs 1 2
    disp!(1, 2, 3); // outputs 1 2 \n 3
    disp!(1, 2, 3, 4); // outputs 1 2 \n 3 4
    disp!(1, 2, 3, 4, 5); // outputs 1 2 \n 3 4 \n 5
    disp!(1, 2, 3, 4, 5, 6); // outputs 1 2 \n 3 4 \n 5 6
    disp!(1, 2, 3, 4, 5, 6, 7); // outputs 1 2 \n 3 4 \n 5 6 \n 7

    println!("{}", join!(1, 2)); //outputs 12
    println!("{}", join!(1, 2, 3)); // outputs 123
    println!("{}", join!(1, 2, 3, 4)); // outputs 1234
    println!("{}", join!(1, 2, 3, 4, 5)); // outputs 12345
    println!("{}", join!(1, 2, 3, 4, 5, 6)); // outputs 123456
    println!("{}", join!(1, 2, 3, 4, 5, 6, 7)); // outputs 1234567
}

The disp! macro is the solution I modified based on your example, and join! is a closer version of the actual use case in my code.

It seems like I still have a lot to learn! Thank you very much for your help :slight_smile:

Thanks @steffahn for the detailed example! I was able to run your updated example and it worked for all of the use cases I had originally posted.

Since my actual example works with collecting results of recursive calls and stitching the results backwards, I used approach suggested by @Michael-F-Bryan, but I would also take a bit more time to understand your example better. For example, I'm not familiar with use of # and tt so I need to do some reading on these.

Thank you for the help! :slight_smile:

FYI, the # is just a random token. It could've been anything else, like . or -, etc. I tend to use # in macros internally since it doesn't appear as an operator in any Rust expressions.

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.