79

Here's a synthetic example of what I want:

macro_rules! define_enum {
    ($Name:ident { $($Variant:ident),* }) => {
        pub enum $Name {
            None,
            $($Variant),*,
        }
    }
}

define_enum!(Foo { A, B });

This code compiles, but if add a comma to it:

define_enum!(Foo { A, B, });
//                     ^

The compilation fails. I can fix it with:

($Name:ident { $($Variant:ident,)* })
//                             ^

but then define_enum!(Foo { A, B }); fails,

How should I write a macro to handle both cases:

define_enum!(Foo { A, B });
define_enum!(Foo { A, B, });

3 Answers 3

87

Make the comma optional

As DK. points out, the trailing comma can be made optional.

Rust 1.32

You can use the ? macro repeater to write this and disallow multiple trailing commas:

($Name:ident { $($Variant:ident),* $(,)? }) => { 
//                                 ^^^^^

Previous versions

This allows multiple trailing commas:

($Name:ident { $($Variant:ident),* $(,)* }) => { 
//                                 ^^^^^

Handle both cases

Or you can handle both cases by... handling both cases:

macro_rules! define_enum {
    ($Name:ident { $($Variant:ident,)* }) => {
        pub enum $Name {
            None,
            $($Variant),*,
        }
    };
    ($Name:ident { $($Variant:ident),* }) => {
        define_enum!($Name { $($Variant,)* });
    };
}

define_enum!(Foo1 { A, B });
define_enum!(Foo2 { A, B, });

fn main() {}

We've moved the main implementation to the version that expects the trailing comma. We then added a second clause that matches the case with the missing comma and rewrites it to the version with a comma.

6
  • 12
    Why bother with that when you can use $(,)* just before the closing brace in the macro pattern? One rule, and works for trailing comma...s. Ok, so it's a little out of spec, but cuts down on repetition tremendously.
    – DK.
    Mar 31, 2017 at 15:21
  • 3
    @DK. the main reason is because I didn't realize you could do that ;-) Sounds like a perfectly cromulent answer to me; please add it!
    – Shepmaster
    Mar 31, 2017 at 15:26
  • 1
    Eh, you were here first, and you've already got four Meaningless Internet Points. Quicker to edit it into yours as an alternative for best of both.
    – DK.
    Mar 31, 2017 at 15:27
  • 8
    @DK. I've edited it in, but I'd still advocate for you to add an answer yourself (and I'd edit it back out of this one). Beyond either of us gaining Magic Internet Points, the voting also allows people who come to this question to pick which answer is better for their uses, which is helpful for subsequent people to know what is the more idiomatic solution.
    – Shepmaster
    Mar 31, 2017 at 15:32
  • 2
    You should be aware that the second and third solutions can match one or more commas. define_enum!(Foo1 { , });
    – Omid
    Feb 16, 2021 at 11:55
25

Change the line

($Name:ident { $($Variant:ident),* }) => {

to

($Name:ident { $($Variant:ident),* $(,)? }) => {

to add an optional comma at the end. This works in stable Rust / 2018 edition. This syntax also works for other separators like a semicolon.

0
1

Another option (if you're using an Incremental TT Muncher) is to use an optional capture of the delimiter + the remaining tokens. Using a slightly different example:

macro_rules! example {

    ($name:ident = $value:expr $(, $($tts:tt)*)?) => {
        println!("{} = {}", stringify!($name), $value);
        example!($($($tts)*)?);
    };

    ($value:expr $(, $($tts:tt)*)?) => {
        println!("{}", $value);
        example!($($($tts)*)?);
    };

    () => {};

}

This macro can then be invoked as, for example:

example! { A = 1, "B", C = 3, "D" }

and the trailing comma can be either included or omitted.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.