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.