49
votes

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, });
2

2 Answers

56
votes

Handle both cases

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.

Make the comma optional

DK. points out an alternative, making the trailing comma itself optional.

This avoids the need to delegate from one implementation to the other.

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),* $(,)* }) => { 
//                                 ^^^^^
13
votes

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.