3

Is it actually not possible to create a macro like this or am I doing it wrong:

sample!("hello", "there") => 

println!("{:?}", "hello");
println!("{:?}", "there");

sample!("hello", "there", a "second type", a "another second type") => 

println!("{:?}", "hello");
println!("{:?}", "there");
println!("second {:?}", "second type");
println!("second {:?}", "another second type");

What I've tried is this (playground link):

macro_rules! sample {
  ( 
    $( $first:literal ),*
    $( a $second:literal ),*
  ) => {
      $(
        println!("{:?}", $first);
      )*
      $(
        println!("second {:?}", $second);
      )*
  };
}

Which fails with:

error: no rules expected the token `a`
  --> main.rs:18:20
   |
1  | macro_rules! sample {
   | ------------------- when calling this macro
...
18 |   sample!("hello", a "testing");
   |                    ^ no rules expected this token in macro call

error: aborting due to previous error
jeanluc
  • 1,608
  • 1
  • 14
  • 28

2 Answers2

6

Rust macros are really strict with their separators.

macro_rules! sample {
  ( 
    $( $first:literal, )*
    $( a $second:literal ),*
  ) => {
      $(println!("{:?}", $first);)*
      $(println!("second {:?}", $second);)*
  };
}

fn main() {
  sample!("hello", a "testing");
}

This sample works, can you spot the change? I moved the comma from outside the first $( ... ) to inside. The difference is:

  • $( $a:literal ),* accepts only "a", "b", "c" (no trailing comma allowed)
  • $( $a:literal, )* accepts only "a", "b", "c", (trailing comma required)

In your macro, the in-between comma doesn't match as part of either the first or second repetition. The error is basically saying it expected another $first instead of a $second since that's what the repetition says.

You can potentially fix it by introducing an optional comma:

macro_rules! sample {
  ( 
    $( $first:literal ),*
    $(,)? // <----------------
    $( a $second:literal ),*
  ) => {
      $(println!("{:?}", $first);)*
      $(println!("second {:?}", $second);)*
  };
}

which is more lenient but would allow weird things like this, which may or may not be okay depending on what you want.

sample!("hello", "there",);
sample!(, a "testing");
sample!("hello" a "testing");

Unfortunately, I don't know a perfect solution without using different arms like so:

macro_rules! sample {
  ($( $first:literal ),*) => { };
  ($( $first:literal, )* $( a $second:literal ),+) => { };
  ($( a $second:literal ),*) => { };
}

fn main() {
  sample!("hello", "there");
  sample!("hello", "there", a "testing");
  sample!(a "second type", a "another second type");
  // sample!("hello", "there",);
  // sample!(, a "testing");
  // sample!("hello" a "testing");
}

See also:

kmdreko
  • 42,554
  • 6
  • 57
  • 106
1

You're doing it almost right- the trailing comma is tripping up the compiler. Removing the comma works just fine. If you are wondering why, it is because macro_rules invocations are quite picky. When it reads the trailing comma, it looks at the next token, which is a, but since that's not a literal, instead of doing the more sensible thing, which is checking the next pattern, it just gives up.

Aiden4
  • 2,504
  • 1
  • 7
  • 24