Skip to content

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

Hard Primitives

Nested repetition and zipping metavariables

Repair two macros while keeping the supplied function call sites unchanged. table! builds named rows of integers. zip_pairs! combines two identifier lists positionally into strings.

The table result

build_table() must return Vec<(String, Vec<i64>)> from this invocation:

table! {
    alpha => 1, 2, 3;
    beta  => 4, 5;
    gamma => ;
    delta => 7;
}

Preserve the row and value order:

Name Values
alpha [1, 2, 3]
beta [4, 5]
gamma []
delta [7]

Empty rows are valid. The macro also accepts zero rows. Every row ends with a semicolon; values within a row are comma-separated, with no trailing comma in this matcher. Row names are identifier tokens converted to text, so they do not need corresponding variables in scope.

Match and reproduce each level

The table matcher has a repetition for rows and another for each row's values:

( $( $name:ident => $( $val:expr ),* ; )* )

Read it from the inside out: zero or more expressions separated by commas, preceded by a name and arrow, then followed by a semicolon. Repeat that whole row zero or more times.

The semicolon is a token inside each repeated row. It is required after the last row too; it is not an optional separator between rows.

In the output template, expand one tuple per row and one expression per value inside that row. Preserve the repetition structure associated with each captured metavariable:

Vec::from([
    $( (stringify!($name).to_string(), Vec::from([ $( $val ),* ])) ),*
])

These two snippets are a matcher and an output template for a macro rule, not standalone Rust expressions outside a macro definition. The surrounding return type supplies the element type for empty vectors.

The starter's inner matcher and output repetition both use +, meaning one or more. Change both to * so gamma => ; is accepted. Its two-level nesting is already present; keep it. Moving $val outside its inner repetition instead produces a diagnostic such as variable val is still repeating at this depth.

Zip identifiers within one repetition

zip_demo() must return ["x-p", "y-q", "z-r"] from:

zip_pairs!(x, y, z ; p, q, r)

The matcher captures two comma-separated identifier lists with a semicolon between them. Use both $a and $b in one output repetition, combining their stringified names with a literal hyphen. concat! can combine the string literals produced by stringify!; convert each result to String.

The current starter emits only the first list's names. Repair its output rule so both lists determine every pair.

For this rule, the two lists must have the same length. Unequal counts fail during macro expansion rather than truncating. Two empty lists are valid; an empty invocation result needs a surrounding Vec<String> type context. This rule does not accept trailing commas.

Check your understanding How does this differ from Iterator::zip? Show answer

Iterator::zip yields pairs until either iterator ends. A macro output repetition driven by these two captured lists requires matching counts. Those are different API rules. Neither requires speculation about what behavior would be reasonable for every other zip operation.

A macro repetition also needs a captured metavariable to determine how many times to expand. With nested input, a depth number alone does not describe every constraint: the output must follow the captures' compatible nesting and repetition counts. Keep the structure of this rule visible as you edit.

The harness selects the table or zip demonstration and may select individual table rows for comparison. Implement the macros themselves rather than replacing the supplied calls with hardcoded results.

Submissions must compile, pass the cases and pass the Rust Clippy gate.