Skip to content

← Macros, FFI and Type-Driven Design step 9 of 28

Hard Primitives

Accumulate enum variants in a recursive macro

Complete build_enum by adding the two recursive rules missing from the starter. The macro generates a public enum and an associated function listing its variants.

build_enum! { Color: Red = 1, Green = 2, Blue = 4 }
build_enum! { Level: Low = 10, High = 20, }

Keep the supplied terminal rule, entry rule, invocations and variants function. The generated enums use repr(u8) and derive Debug, Clone, Copy, PartialEq and Eq. Each enum's all function returns Vec<(&'static str, u8)> in declaration order. The supplied variants function combines Color's entries followed by Level's.

Support a nonempty list of distinct variant names with distinct integer literal discriminants in the u8 range, with or without one trailing comma. General expressions, duplicate discriminants and empty enums are outside this task.

Move one variant into the accumulator

The public entry rule starts a recursive invocation with an empty bracketed accumulator. Each recursive rule moves one name/value pair from the remaining input to the end of that accumulator. For Color, the successive inputs are:

@acc Color []                          Red = 1, Green = 2, Blue = 4
@acc Color [Red = 1,]                  Green = 2, Blue = 4
@acc Color [Red = 1, Green = 2,]        Blue = 4
@acc Color [Red = 1, Green = 2, Blue = 4,]

These lines show macro input tokens, not separate Rust statements. At the last step, no input remains, so the terminal rule emits the complete enum and impl. This pattern is called push-down accumulation.

Add two rules at the marked location:

  1. Match a variant followed by a comma and any remaining tokens. Append the variant and comma inside the brackets, then recurse with the rest.
  2. Match the final variant without a comma. Append it with a comma, then recurse with no remaining tokens.

Both paths leave the accumulator comma-terminated. Append rather than prepend so the output keeps the original order. Each call must consume a variant; recursing on unchanged input would never reach the terminal rule.

Why keep tokens in brackets?

The brackets separate completed variants from unprocessed input. The pattern [$($done:tt)*] captures the tokens inside that delimited group. The recursive rules carry them forward without needing to interpret the completed variants.

The terminal rule instead matches [$($vn:ident = $vv:literal,)*]. It needs the names and values separately to generate two different forms: enum variants and tuples in all. Keeping those fields structured at the final step makes both outputs straightforward.

This is a useful pattern, not a requirement for every enum-generating macro. This particular grammar could also be handled by a single repetition. The exercise uses recursion to practise carrying partial output between rules.

Internal tags and rule order

The @acc prefix is a convention used by this macro. It is not access control: code that can invoke build_enum can also supply that prefix directly. The public entry pattern starts with an identifier followed by a colon, so it cannot match an @acc invocation.

Rust tries macro rules in order and uses the first match. Put specific rules before broad catch-all patterns when they overlap. The supplied entry rule is not a catch-all; keeping internal rules together here is an organizational choice, rather than a universal ordering requirement.

See the macro_rules reference.

What the harness returns

The input selects a variant by its exact, case-sensitive name. Red, Green, Blue, Low and High return 1, 2, 4, 10 and 20 respectively. Unknown names return -1. A missing or empty name returns the complete ordered list:

[["Red", 1], ["Green", 2], ["Blue", 4], ["Low", 10], ["High", 20]]

The starter fails because its entry rule recurses with unprocessed variants, but no existing rule accepts that form. Passing requires compilation, correct outputs and Clippy with warnings denied.