Declarative Macros (macro_rules!)
A declarative macro is a compile-time pattern matcher, defined with macro_rules! , that takes the tokens you pass it and expands them into real Rust code before the program is compiled.
Learn Declarative Macros (macro_rules!) in our free Rust course — a beginner-friendly interactive lesson with worked examples, a practice exercise and a…
Part of the free Rust course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.
In this lesson you'll learn macro matcher syntax, fragment specifiers like $x:expr , repetition with $(...)* , hygiene, and you'll build your own vec! -style and hashmap! macros.
What You'll Learn in This Lesson
1️⃣ Your First macro_rules!
A macro definition looks like macro_rules! name {' '} . Inside, each rule has the shape (matcher) => {' '} . The matcher describes the tokens the macro accepts; $x:expr captures an expression and binds it to $x , which you then splice into the expansion. Invoke a macro with a trailing ! , as in square!(4) .
Notice that square!(2 + 1) gives 9 , not 5. A :expr fragment is captured as one complete expression unit, so the expansion behaves like (2 + 1) * (2 + 1) rather than naive text substitution.
2️⃣ Repetition — Building a vec!-like Macro
The real power of macros is repetition . The matcher $( $item:expr ),* means "zero or more expressions separated by commas." In the expansion, wrapping code in $( ... )* repeats it once per captured item. Adding $(,)? at the end allows an optional trailing comma — exactly how the standard vec! works.
Use * for "zero or more" and + for "one or more." The separator (here a comma) goes between the closing ) and the repetition operator in the matcher.
3️⃣ Multiple Rules, Recursion & Hygiene
A macro can list several rules ; the compiler tries them top to bottom and uses the first that matches. Macros may even call themselves recursively. And thanks to hygiene , any local variable a macro introduces (like a and b below) is kept separate from variables of the same name at the call site — no accidental clashes.
The single-argument rule is the base case; the multi-argument rule peels off the first value and recurses on $($rest),+ . Even though both the macro and main use a variable named a , our value 100 is untouched.
Your turn. Fill in the blank marked ___ , then run it.
Combine repetition with a custom separator ( => ) to build a macro that constructs a HashMap . Run it with cargo run and check the output.
📋 Quick Reference — Macro Syntax
Practice quiz
Which keyword defines a declarative macro?
- macro
- define!
- macro_rules!
- fn!
Answer: macro_rules!. Declarative macros are defined with macro_rules! name { ... }.
When does a macro do its work?
- At compile time
- At runtime
- During linking
- Never
Answer: At compile time. Macros expand into code at compile time, before type-checking.
What does the fragment specifier :expr capture?
- An identifier
- A type
- A whole block only
- An expression
Answer: An expression. :expr captures one complete expression as a single unit.
Why does square!(2 + 1) give 9 rather than 5?
- Macros add a bonus
- 2 + 1 is captured as one expression, like (2+1)*(2+1)
- It rounds up
- It uses integer overflow
Answer: 2 + 1 is captured as one expression, like (2+1)*(2+1). An :expr fragment is one unit, so the expansion behaves like (2+1)*(2+1).
What does the matcher $( $item:expr ),* mean?
- Zero or more comma-separated expressions
- Exactly one expression
- One or more, semicolon-separated
- A single identifier
Answer: Zero or more comma-separated expressions. $(...),* matches zero or more items separated by commas.
What is the difference between * and + in repetition?
- No difference
- * is one or more, + is zero or more
- * is zero or more, + is one or more
- Both mean exactly one
Answer: * is zero or more, + is one or more. * means zero or more matches; + means one or more.
What does $(,)? at the end of a matcher allow?
- A required comma
- An optional trailing comma
- A semicolon
- Two commas
Answer: An optional trailing comma. $(,)? permits an optional trailing comma, like the real vec! macro.
What does macro hygiene prevent?
- Slow macros
- Compile errors
- Recursion
- Macro-internal names colliding with names at the call site
Answer: Macro-internal names colliding with names at the call site. Hygiene keeps a macro's own variables separate from the caller's.
When a macro has multiple rules, which one is used?
- A random one
- The first rule that matches
- The last rule
- All of them
Answer: The first rule that matches. The compiler tries rules top to bottom and uses the first match.
How are declarative and procedural macros different?
- They are identical
- Procedural are simpler
- Declarative match token patterns; procedural run code on a TokenStream
- Declarative run at runtime
Answer: Declarative match token patterns; procedural run code on a TokenStream. macro_rules! matches patterns; procedural macros transform a TokenStream with code.