We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Macros, FFI and Type-Driven Design step 14 of 28
Building std's macros: vec!, matches!, a HashMap literal
Rebuild three macros you have used hundreds of times, and watch the magic evaporate.
pub fn run(xs: Vec<i64>) -> (Vec<i64>, Vec<bool>, Vec<(String, i64)>)
Three macros to finish:
-
my_vec!— the list form with a trailing comma, plus[elem; n]. -
my_matches!—my_matches!(expr, pattern)andmy_matches!(expr, pattern if guard). -
hashmap!—hashmap!{ k => v, … }with a trailing comma, pre-sized withwith_capacityusing the stable counting idiom from 18.7.
Every one of these is a plain macro_rules! definition in the standard library.
Not a compiler intrinsic, not a special form. vec! is thirty lines you could
have written.
my_vec! — same three arms as the real thing
You did the list and repeat arms in 18.5. The real vec! looks like this:
macro_rules! vec {
() => ( Vec::new() );
($elem:expr; $n:expr) => ( from_elem($elem, $n) );
($($x:expr),+ $(,)?) => ( <[_]>::into_vec(Box::new([$($x),+])) );
}
```
`<[_]>::into_vec(Box::new([...]))` builds a fixed-size array, boxes it, and
converts the box into a `Vec` **without reallocating** — the allocation is
already the right size and the right layout. That is why `vec![1, 2, 3]` costs
exactly one allocation and nothing else.
The starter's list arm already uses that formulation; it just needs `$(,)?` and
the `[elem; n]` arm.
## `my_matches!` — the tricky one
($e:expr, $p:pat $(if $g:expr)?) => {
match $e {
$p $(if $g)? => true,
_ => false,
}
};
Three things are happening at once.
**The optional group.** `$(if $g:expr)?` makes the guard optional, and the same
`$(if $g)?` appears in the transcriber. Both halves must agree; you cannot make
the matcher optional and the transcriber unconditional.
**The follow set.** `$p:pat` is followed by `if` — which is legal, because `if`
is one of the five tokens in `pat`'s follow set (18.12). This is not a
coincidence: `if` is in that list precisely so that `matches!` can be written.
**Order matters inside the arm.** `$p $(if $g)? => true` puts the guard between
the pattern and the `=>`, which is where a `match` arm wants it. Emit them in
the wrong order and you get a parse error inside the expansion.
Note that `$p:pat` in edition 2024 accepts a top-level or-pattern, so
`my_matches!(n, 1 | 2)` works with no extra arm. That is the change that removed
`|` from `pat`'s follow set — you get the feature, you lose the follower.
## `hashmap!` — counting, and the empty case
```rust
hashmap! {
"alpha".to_string() => 1i64,
"beta".to_string() => 2i64,
}
```
Two arms. The list arm should pre-size the map:
```rust
let mut m = HashMap::with_capacity(count_tts!($($k),*));
$( m.insert($k, $v); )+
m
count_tts! is the array idiom from 18.7, already written for you. Pre-sizing is
the whole reason to bother: without it a five-entry literal reallocates as it
grows, which is silly when the count is sitting right there in the source.
The empty case needs its own arm, and the reason is a lint. If you try to
serve hashmap!{} from the list arm, the expansion contains let mut m = …
with no inserts, and rustc’s warn-by-default unused_mut fires:
warning: variable does not need to be mutable
Under clippy -D warnings that fails your submission — and it fails it at the
call site of the empty invocation, which is a genuinely confusing place for it
to appear. Giving the empty case its own arm that just says HashMap::new() is
exactly what vec! does, and now you know why.
::: question clippy has lints called match_like_matches_macro and useless_vec. Won’t they fire on code that implements those macros?
useless_vec will, if you invoke vec! where a slice or array would do — it is
about call sites, not definitions, so building your own Vec machinery is safe.
match_like_matches_macro is the more interesting one. It fires on a match
whose arms only produce true and false, telling you to use matches!
instead — which is precisely the shape of a correct my_matches! implementation.
It does not fire here, because clippy suppresses a range of style lints inside
macro expansions; the whole point of the lint is to improve hand-written code,
and code produced by a macro was not hand-written.
That behaviour is worth knowing in both directions. It means implementing a standard-library macro will not fight the linter. It also means clippy is quieter inside your macros than outside them, so a mistake that a lint would normally catch can hide in an expansion. :::
Your job
Finish all three. The starter compiles none of the six invocations in run.
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.