We can't find the internet
Attempting to reconnect
Something went wrong!
Attempting to reconnect
← Macros, FFI and Type-Driven Design step 2 of 28
Your first macro_rules!: matchers and transcribers
Write a macro with four rules, then let a match pick which one to invoke.
macro_rules! describe { /* ... */ }
pub fn run(n: usize) -> String
run is already written. It calls describe! seven times with seven different
argument lists, and your job is to make the macro answer correctly:
| invocation | answer |
|---|---|
describe!() |
"nothing" |
describe!(one) |
"one thing" |
describe!(one, two) |
"two things" |
| anything else |
"something else" |
The starter has the first rule and nothing else, so it does not compile.
What a macro rule actually is
A macro_rules! definition is a list of rules, and every rule has two
halves separated by =>:
macro_rules! describe {
// ┌── matcher ┌── transcriber
(one, two) => { "two things" };
}
The matcher is a pattern over tokens. The transcriber is the tokens
that get emitted in place of the invocation. describe!(one, two) is not a
function call — nothing is passed, nothing returns. The compiler finds the
first rule whose matcher accepts the tokens between the delimiters, deletes the
whole invocation, and pastes the transcriber’s tokens in its place. Only then
does the rest of compilation — name resolution, type checking, borrow checking —
begin.
That ordering is the single most useful fact about macros. It explains why a
macro can produce a struct (types are not involved yet), why the error
messages point at generated code (the code you wrote is gone by then), and why
describe!(one, two) can accept one and two even though no such variables
exist (they are never resolved — they are just tokens that the matcher compares
against literal tokens one and two).
First match wins, top to bottom
Rules are tried in source order, and the first one that matches wins. There is no “most specific rule” heuristic and no scoring. This is the rule that most often bites beginners, because the natural instinct is to write the general case first:
macro_rules! describe {
($($anything:tt)*) => { "something else" }; // catches EVERYTHING
(one) => { "one thing" }; // dead. never reached.
}
$($anything:tt)* means “zero or more token trees”, which every possible
invocation satisfies. Put it first and every later rule becomes unreachable
code — and by default the compiler says nothing at all. (unused_macro_rules is
an allow-by-default rustc lint; item 18.13 shows how to turn it on when you
suspect this.)
The test cases here are chosen so a catch-all-first ordering fails loudly.
::: question describe!(one) and describe!{one} and describe![one] — do all three work, and are they different?
All three work, and the delimiters are interchangeable. The macro’s matcher
never sees the outer delimiter at all; it only sees the tokens inside. So
describe!(one), describe!{one} and describe![one] are the same invocation
written three ways, and one rule matches all three.
There is exactly one difference, and it is about statements, not matching. In statement position, a brace-delimited invocation needs no trailing semicolon:
my_macro! { ... } // no semicolon needed
my_macro!( ... ); // semicolon needed
That is why vec![...] and println!(...) read the way they do while
macro_rules! { ... } and thread_local! { ... } read the way they do. The
convention in the community is !() for expression-like macros, ![] for
collection-like ones, and !{} for item-like ones — a convention only, which
clippy’s allow-by-default nonstandard_macro_braces lint can enforce if you ask
it to.
:::
Definition order matters
A macro_rules! macro is only in scope after its textual definition. This
is a different rule from everything else in Rust, where a function defined at
the bottom of a file is callable from the top. Move the macro_rules! block
below run here and nothing compiles.
The reason is the same as before: macros are expanded before name resolution
runs, in one pass over the token stream. There is no earlier pass that could
have collected the definitions. (Item 10.10 covers #[macro_export] and
pub(crate) use, which is how macros escape this rule at module boundaries.)
Your job
Add three rules to describe! so that all seven invocations in run answer
correctly. Think about the order before you type.
Stuck?
Rust reference solution
Sign in to attempt this problem and reveal the reference solution.