We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Macros, FFI and Type-Driven Design step 6 of 28
Nested repetition and zipping metavariables
A repetition inside a repetition — the step from toy macros to the ones that build routing tables, test matrices and configuration blocks.
macro_rules! table { /* ... */ }
pub fn build_table() -> Vec<(String, Vec<i64>)>
The call site is fixed:
table! {
alpha => 1, 2, 3;
beta => 4, 5;
gamma => ;
delta => 7;
}
and must produce
[("alpha", [1, 2, 3]), ("beta", [4, 5]), ("gamma", []), ("delta", [7])]
Note gamma. A row with no entries is legal, and it is what forces you to
think about * versus +.
There is a second macro, zip_pairs!, that pairs two lists positionally:
zip_pairs!(x, y, z ; p, q, r) gives ["x-p", "y-q", "z-r"].
Depth must mirror exactly
The matcher for the table has two levels:
( $( $name:ident => $( $val:expr ),* ; )* )
//^outer ^inner
The outer repetition runs once per row; the inner one runs once per value. So
$name` is captured at depth 1 and `$val at depth 2.
The transcriber must reproduce that nesting exactly. $name` may only appear
inside one repetition; `$val may only appear inside two:
Vec::from([
$( (stringify!($name).to_string(), Vec::from([ $( $val ),* ])) ),*
// ^outer again ^inner again
])
Get the depth wrong and you get one of the worst error messages in Rust:
error: variable `val` is still repeating at this depth
which is accurate and completely opaque on first contact. It means: you used a
depth-2 metavariable somewhere the compiler was only iterating one level, so it
does not know which of the several $val`s you meant.
Note also that `;` here is not a separator — it is a literal token inside the
outer repetition's body. That is what allows the trailing `;` after `delta => 7`
without any `$(,)?-style trick.
* versus +, and why gamma is in the test
+ means one or more. Write the inner repetition as $( $val:expr ),+ and
gamma => ; fails to compile — the matcher demands at least one value and there
is none. The starter does exactly this, so it does not compile until you fix it.
The same choice applies to the transcriber: if the matcher used *, the
transcriber’s matching repetition must also allow zero.
Zipping two metavariables
Two metavariables can be used in one transcriber repetition if they came from repetitions at the same depth that bound the same number of fragments:
( $($a:ident),* ; $($b:ident),* ) => {
Vec::from([ $( concat!(stringify!($a), "-", stringify!($b)).to_string() ),* ])
};
zip_pairs!(x, y, z ; p, q, r) pairs them positionally: x with p, y with
q, z with r.
Two constraints, both hard errors rather than silent truncation:
-
The counts must be equal.
zip_pairs!(x, y ; p, q, r)is rejected — the macro system will not quietly dropr. - The depths must match. You cannot zip something captured at depth 1 with something captured at depth 2.
::: question Why can’t the compiler just truncate to the shorter list, the way Iterator::zip does?
Because the two are not the same kind of operation, and treating them alike would lose information the compiler has and cannot recover later.
Iterator::zip runs at run time over two sequences whose lengths are usually
unknown until they are consumed; stopping at the shorter one is the only
reasonable behaviour. A macro repetition runs at compile time over two token
lists whose lengths are right there in the source. If they disagree, the person
who wrote the invocation made a mistake — probably a missing element — and
silently dropping the excess would produce code that compiles and does the wrong
thing. Exactly the failure mode item 18.4 was about.
So the compiler reports it. This is the same instinct as #[must_use] on
Result and as exhaustive match: where the information exists at compile time,
Rust prefers to make you resolve the ambiguity rather than pick for you.
:::
Your job
Fix table! so the empty row is legal and the nesting is right, and fix
zip_pairs! so it pairs both lists instead of ignoring the second.
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.