Skip to content

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

Easy Primitives

Macro repetition: lists and repeated values

Complete my_vec! so it accepts an empty invocation, a comma-separated list with an optional trailing comma, and a repeated-value form. Keep build() unchanged; its six calls should produce these rows:

Invocation Row
my_vec![] []
my_vec![1, 2, 3] [1, 2, 3]
my_vec![4, 5, 6,] [4, 5, 6]
my_vec![7; 4] [7, 7, 7, 7]
my_vec![9] [9]
my_vec![8; 0] []

The starter already handles empty and nonempty lists. It cannot compile the supplied calls until you add trailing-comma support and the semicolon form.

Repeat a pattern, then repeat its output

A repetition has the shape $(pattern)separator operator. The separator is optional; the operator controls how many matches are allowed:

Operator Matches
* Zero or more
+ One or more
? Zero or one, with no separator allowed

For example, $($x:expr),+ captures one or more expressions separated by commas. In the output, $($x),+ emits those expressions with commas between them. The starter uses that output to construct an array and convert it into a vector:

($($x:expr),+) => {
    Vec::from([$($x),+])
};

Because $x was captured repeatedly, emitting it requires a corresponding repetition. Writing a single $x outside that repetition does not select one element. Conversely, an output repetition needs a captured variable to determine how many copies to emit.

Accept one trailing comma

Keep + for the nonempty list and add $(,)? after it:

($($x:expr),+ $(,)?) => { /* build the vector */ };

The first comma separates expressions. The optional group consumes a comma after the last expression. These do different jobs: 1, 2, 3, needs both. The existing () arm handles the empty case, so this list arm does not need to accept zero expressions. A lone comma is not a supported invocation.

Repeat a value, not its expression

Add a separate arm matching ($elem:expr; $n:expr). Evaluate the element once, then produce a vector of length n containing that value and its clones. std::iter::repeat_n(elem, n).collect::<Vec<_>>() provides this behavior. The count must have type usize, and the element type must implement Clone.

For example, my_vec![String::from("hi"); 3] produces three strings. It does not call String::from three times. The element expression is still evaluated once when the count is zero, and its value is then dropped. The list form instead evaluates each supplied expression once and moves its result into the vector; its elements do not need to implement Clone.

The semicolon arm may appear before or after this comma-list arm. Matching a rule requires the whole invocation to match: seeing 7 does not make the list rule accept 7; 4. If that rule fails to match, Rust tries another arm. This differs from an error in code emitted by an already matched arm, which does not cause Rust to try a different expansion.

What the harness checks

build() returns Vec<Vec<i32>>. The harness returns every row when no row indices are supplied, or selects the requested rows otherwise. Preserve the calls in build() so each supported invocation is exercised. This exercise checks vector contents, not allocation strategy or performance equivalence with the standard library's vec! macro.