Skip to content

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

Easy Primitives

Repetition: $(...),* and friends

Rebuild the part of vec! that makes it feel native.

macro_rules! my_vec { /* ... */ }

pub fn build() -> Vec<Vec<i32>>

build exercises six invocations and returns their results as 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] []

Variadics were the original motivation for macros (18.1), and repetition is the mechanism that delivers them.

The syntax

$( ... )SEP OP
```

- `$( ... )` wraps the part that repeats.
- `SEP` is an optional separator token — usually `,`, sometimes `;` or `|`. It
  sits **before** the operator, not after.
- `OP` is one of `*` (zero or more), `+` (one or more), `?` (zero or one).

So `$($x:expr),*` reads "zero or more `expr` fragments, separated by commas".

`?` is special in two ways: it means "optional", and it is **the only operator
that forbids a separator**. `$(,)?,*` is a syntax error, and so is
`$( $x:expr ),?`. A separator only makes sense when a thing can repeat.

The transcriber uses the same shape, and this is the part that surprises people:

```rust
($($x:expr),* $(,)?) => {
    Vec::from([$($x),*])       // <- the repetition appears again, in the output
};

Every metavariable captured under a repetition must be used under a matching repetition. Write $x` outside the `$( ) and you get an error; write a transcriber repetition that mentions no metavariable at all and you get “attempted to repeat an expression containing no syntax variables”, because the compiler has no way to know how many times to repeat.

$(,)?` is the whole quality-of-life difference Compare: ```rust my_vec![ 1, 2, 3, // <- rustfmt puts this comma here. So does every code reviewer. ] ``` Without `$(,)? at the end of your matcher, that fails to compile with “unexpected

end of macro invocation”, and every user of your macro learns to resent it. vec!, println!, matches!, assert_eq! all accept a trailing comma, and so should yours. It costs five characters.

Note the placement: ($($x:expr),* $(,)?)`. The `,` is the *separator* of the main repetition, and `$(,)? is a separate, optional, at-most-once group after it. They are two different commas doing two different jobs.

The [elem; n] arm

vec![0u8; 1024] is the syntax that no function could ever accept (18.1) — it is not an expression, it is a shape the macro invents. You need a dedicated rule:

($elem:expr; $n:expr) => { /* ... */ };

; is one of the three tokens legally allowed to follow an expr fragment (the others are , and =>) — see 18.12 for why there is a list at all.

Filling the vector needs the element n times, which means it must be cloneable. std::iter::repeat_n states that requirement in its own signature, so you do not have to write a .clone() call that clippy will then object to for Copy types.

Put this rule before the list rule. For my_vec![1, 2, 3] the ; matcher fails harmlessly at the , and falls through; but for my_vec![7; 4] the list rule would match 7 and then choke on ;.

::: question What does the real vec! do for the list form?

Very nearly this, and it is worth seeing that there is no magic:

macro_rules! vec {
    () => ( Vec::new() );
    ($elem:expr; $n:expr) => ( from_elem($elem, $n) );
    ($($x:expr),+ $(,)?) => ( <[_]>::into_vec(Box::new([$($x),+])) );
}
```

Three rules, the same three shapes you are writing, plus `$(,)?`. The list arm
builds a fixed-size array on the stack, boxes it, and converts the box into a
`Vec` without reallocating — which is why `vec![1, 2, 3]` is exactly as fast as
writing the array out by hand.

`Vec::from([$($x),+])` gets you the same result with less ceremony, and is what
you should write today. Item 18.14 rebuilds the real thing, including the
`with_capacity` counting trick.
:::

## Your job

The starter has the empty rule and a list rule with no trailing-comma support and
no `[elem; n]` form, so it does not compile. Add what is missing.