We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Macros, FFI and Type-Driven Design step 5 of 28
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.
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.