Syntax Cache
BlogMethodFeaturesHow It WorksBuild a Game
  1. Home
  2. Rust
  3. Rust Macros Practice: macro_rules!, Declarative Macros
Rust25 exercises

Rust Macros Practice: macro_rules!, Declarative Macros

Practice Rust declarative macros including macro_rules! syntax, pattern matching, repetition operators, and common macro patterns.

Cheat SheetCommon ErrorsQuick ReferencePractice
Warm-up1 / 2

Can you write this from memory?

Declare a macro named `say_hello` that expands to `println!("Hello!");`.

On this page
  1. 1Fragment Specifier Cheat Sheet
  2. 2Compiler Error: "no rules expected the token"
  3. 3The macro_rules! Template
  4. 4Repetition Patterns
  5. 5Multiple Arms (Most Specific First)
  6. 6Local Ambiguity: Overlapping Patterns
  7. 7Macro Hygiene
  8. 8Debugging Macros: cargo expand
  9. 9macro_rules! vs Procedural Macros
  10. 10Further Reading
Fragment Specifier Cheat SheetCompiler Error: "no rules expected the token"The macro_rules! TemplateRepetition PatternsMultiple Arms (Most Specific First)Local Ambiguity: Overlapping PatternsMacro HygieneDebugging Macros: cargo expandmacro_rules! vs Procedural MacrosFurther Reading

Macros in Rust generate code at compile time. macro_rules! creates declarative macros using pattern matching. You've used them: println!, vec!, format!.

The syntax is unusual---fragment specifiers like $x:expr, repetition with $(...)*, and match arms that look almost like regular Rust but aren't quite.

This page focuses on macro basics so you can read and write simple macros.

Related Rust Topics
Rust Foundations: Variables, Types & Basic SyntaxRust Traits & Generics: Trait Bounds, impl Trait, TurbofishRust Derive Traits: Debug, Clone, PartialEq & More

Fragment specifiers tell the macro parser what to expect: expr for expressions, ident for identifiers, ty for types, tt for anything. Using the wrong specifier causes "no rules expected the token" errors.

Fragment specifiers tell the macro parser what kind of syntax to expect:

SpecifierMatchesExample input
exprAny expression1 + 1, foo(), x
identSingle identifierfoo, my_var
tyTypei32, Vec<String>
patPattern_, Some(x), 1..=5
stmtStatementlet x = 5
blockBlock expression{ ... }
itemItem (fn, struct, impl)fn foo() {}
pathType pathstd::io::Error
literalLiteral value42, "hello", true
ttAny single token treeAnything (most flexible)
metaAttribute contentderive(Debug)
lifetimeLifetime'a, 'static
visVisibilitypub, pub(crate), (empty)

tt (token tree) is the escape hatch — it matches any single token or balanced group in brackets. Use it for forwarding arbitrary tokens to other macros.

Ready to practice?

Start practicing Rust Macros: macro_rules!, Declarative Macros with spaced repetition

The most common macro error. Your fragment specifier doesn't match the input:

// WRONG: ident only matches a single identifier
// macro_rules! double {
//     ($x:ident) => { $x * 2 };
// }
// double!(1 + 1);  // error: no rules expected `1`

// RIGHT: use expr for expressions
macro_rules! double {
    ($x:expr) => { $x * 2 };
}
let result = double!(1 + 1); // 4

ident matches only a single identifier like x or foo. An expression like 1 + 1 requires expr.

// Simplest macro: no arguments
macro_rules! say_hello {
    () => {
        println!("Hello!");
    };
}
say_hello!();

// Capture an identifier and generate code
macro_rules! create_function {
    ($name:ident) => {
        fn $name() {
            println!("Called {}", stringify!($name));
        }
    };
}
create_function!(foo);
foo(); // prints "Called foo"

Semicolons between arms are required. A trailing semicolon after the last arm is conventional. The pattern-matching syntax inside macro_rules! is inspired by regular pattern matching, though the macro version operates on tokens rather than values.

$($x:expr),* matches zero or more comma-separated items. + for one or more. Add $(,)? after the repetition to accept optional trailing commas.

SyntaxMatchesExample
$($x:expr),*Zero or more, comma-separated1, 2, 3 or empty
$($x:expr),+One or more, comma-separated1, 2, 3 (not empty)
$($x:expr),* $(,)?Zero or more with optional trailing comma1, 2, 3,
// Build a Vec from comma-separated expressions
macro_rules! vec_of {
    ($($x:expr),* $(,)?) => {
        {
            let mut v = Vec::new();
            $(v.push($x);)*
            v
        }
    };
}

let v = vec_of![1, 2, 3];
let v2 = vec_of![1, 2, 3,]; // trailing comma OK
// Generate a function with a captured name and type
macro_rules! make_adder {
    ($name:ident, $t:ty) => {
        fn $name(x: $t, y: $t) -> $t {
            x + y
        }
    };
}

make_adder!(add_i32, i32);
let sum = add_i32(2, 3); // 5

Arms are matched top-to-bottom. Put specific patterns before general ones:

macro_rules! log {
    ($msg:expr) => {
        println!("[LOG] {}", $msg)
    };
    ($fmt:expr, $($arg:tt)*) => {
        println!("[LOG] {}", format!($fmt, $($arg)*))
    };
}

log!("simple message");
log!("hello {}", "world");

When two arms could both match the same input, the parser cannot decide:

// WRONG: both arms match a single expr
// macro_rules! ambiguous {
//     ($x:expr) => { println!("one: {}", $x) };
//     ($x:expr, $y:expr) => { println!("two: {} {}", $x, $y) };
// }
// ambiguous!(1, 2);  // error: local ambiguity

// RIGHT: add a distinguishing token
macro_rules! log {
    (single: $x:expr) => { println!("one: {}", $x) };
    (pair: $x:expr, $y:expr) => { println!("two: {} {}", $x, $y) };
}
log!(single: 1);
log!(pair: 1, 2);

The issue: $x:expr in the first arm can match 1, 2 as a single expression containing the comma operator. Adding a keyword prefix or restructuring the arms removes the ambiguity.

Macros in Rust are partially hygienic — variables defined inside the macro expansion don't leak into the caller's scope:

macro_rules! make_var {
    () => {
        let x = 42;  // this x is scoped to the expansion
    };
}

let x = 0;
make_var!();
println!("{x}"); // still 0 — the macro's x is separate

However, macros can access items (fn, struct, etc.) from the caller's scope. When passing identifiers to macros, the generated code uses the caller's namespace. The traits and generics guide explains how generics often provide a cleaner alternative to macros when the goal is type-parametric code.

Macro errors point at the call site, not the expansion. Use cargo expand to see generated code:

cargo install cargo-expand
cargo expand           # expand all macros in the crate
cargo expand my_module # expand a specific module

For quick debugging, stringify!() shows what tokens were captured:

macro_rules! debug_me {
    ($($t:tt)*) => {
        println!("tokens: {}", stringify!($($t)*));
    };
}
debug_me!(hello world 42); // prints: tokens: hello world 42

macro_rules! uses pattern matching on tokens (simple to moderate). Procedural macros run arbitrary Rust code in a separate crate (for #[derive], attribute macros, etc.). Prefer functions when possible—macros are harder to debug.

This page covers declarative macros (macro_rules!). Rust also has procedural macros, which are different:

macro_rules!Procedural macros
Defined withPattern matching on tokensRust code in a proc-macro crate
Examplesvec!, println!#[derive(Debug)], #[tokio::main]
ComplexitySimple to moderateArbitrary code generation

If you're looking for #[derive(...)] traits, the derive traits guide covers which traits can be derived and when to implement manually. If you need attribute macros like #[test] or #[tokio::main], those are procedural macros.

  • The Rust Book: Macros — declarative vs procedural overview
  • Rust By Example: macro_rules! — practical examples
  • The Little Book of Rust Macros — deep dive into macro patterns
  • Rust Reference: Macros By Example — formal syntax and fragment specifiers

When to Use Rust Macros: macro_rules!, Declarative Macros

  • Use macros to reduce repetitive code patterns.
  • Use macros for DSLs (domain-specific languages).
  • Use macros when functions cannot express what you need (e.g., variadic args).
  • Prefer functions when possible. Macros are harder to debug.
  • Use `#[macro_export]` to make macros available outside the module.

Check Your Understanding: Rust Macros: macro_rules!, Declarative Macros

Prompt

What is the difference between macros and functions in Rust?

What a strong answer looks like

Functions operate on values at runtime. Macros operate on syntax at compile time—they see tokens, not values. Macros can be variadic, generate code, and do things impossible with functions. But they are harder to debug and have unusual syntax.

What You'll Practice: Rust Macros: macro_rules!, Declarative Macros

Define macros with macro_rules!Use fragment specifiers ($x:expr, $name:ident)Apply repetition patterns with $()*Handle optional trailing commasWrite macros with multiple match armsUse stringify! and concat! in macrosExport macros with #[macro_export]Understand macro hygiene

Common Rust Macros: macro_rules!, Declarative Macros Pitfalls

  • Symptom: `error: no rules expected the token`. Why: You used the wrong fragment specifier---e.g., `$x:ident` when the caller passes an expression like `1 + 1`. Fix: Match the specifier to the input: `expr` for expressions, `ident` for identifiers, `ty` for types, `tt` for arbitrary tokens.
  • Symptom: `error: macro expansion ignores token` or unexpected parse failures. Why: You forgot the semicolon between macro arms. Fix: Put semicolons between arms; a trailing semicolon after the last arm is conventional and keeps diffs cleaner.
  • Symptom: Macro compiles but produces wrong output or silently drops values. Why: Macros operate on tokens, not values---they don't evaluate arguments the way functions do, and repeated use of a captured expression evaluates it multiple times. Fix: Bind complex expressions to a `let` variable inside the expansion to evaluate once.
  • Symptom: Callers get a syntax error when adding a trailing comma like `my_macro!(1, 2, 3,)`. Why: Your repetition pattern doesn't allow an optional trailing separator. Fix: Add `$(,)?` after the repetition: `$($x:expr),* $(,)?`.
  • Symptom: You're writing a macro but struggling with debugging and error messages. Why: Macros are inherently harder to debug than functions---error messages point at the call site, not the expansion. Fix: Prefer functions when possible. When you must use macros, use `cargo expand` to see the generated code and `stringify!()` to inspect captured tokens.

Rust Macros: macro_rules!, Declarative Macros FAQ

What are fragment specifiers?

`$x:expr` means "capture an expression as $x." Other specifiers: `ident` (identifier), `ty` (type), `pat` (pattern), `stmt` (statement), `block`, `item`, `tt` (token tree).

What does $(...)* mean?

Repetition. `$($x:expr),*` matches zero or more comma-separated expressions. Use `+` for one or more. Use `?` for zero or one.

What is tt (token tree)?

`tt` matches any single token or a balanced group in brackets. It is the most flexible fragment specifier, useful for forwarding arbitrary tokens.

Why use macros instead of generics?

Macros work on syntax (can generate new identifiers, types, code). Generics work on types (same code, different types). Use generics when possible; macros when you need code generation.

How do I debug macros?

Use `cargo expand` to see generated code. Start simple and add complexity. Test each pattern individually. Use `stringify!` to see what was captured.

Rust Macros: macro_rules!, Declarative Macros Syntax Quick Reference

Basic macro
macro_rules! hi { () => { println!("hi"); }; }
With expr
macro_rules! double { ($x:expr) => { $x * 2 }; }
With ident
macro_rules! make_fn { ($n:ident) => { fn $n() { println!("called"); } }; }
Repetition
($($x:expr),*) => { $($x);* }
Optional comma
($($x:expr),* $(,)?)
Multiple arms
macro_rules! log { () => { println!("empty"); }; ($x:expr) => { println!("{x}", x = $x); }; }
Stringify
stringify!($name)
Export
#[macro_export]
macro_rules! my_macro { () => { 42 }; }

Rust Macros: macro_rules!, Declarative Macros Sample Exercises

Example 1Difficulty: 1/5

Fill in the syntax to invoke the macro `say_hello`.

!()
Example 2Difficulty: 1/5

Fill in the fragment specifier to capture any expression.

:expr
Example 3Difficulty: 1/5

Fill in the fragment specifier to capture an identifier (function/variable name).

:ident

+ 22 more exercises

Further Reading

  • Rust Newtype Pattern: Catch Unit Bugs at Compile Time18 min read

Start practicing Rust Macros: macro_rules!, Declarative Macros

Free daily exercises with spaced repetition. No credit card required.

← Back to Rust Syntax Practice
Syntax Cache

Build syntax muscle memory with spaced repetition.

Product

  • Pricing
  • Our Method
  • Daily Practice
  • Design Patterns
  • Interview Prep

Resources

  • Blog
  • Compare
  • Cheat Sheets
  • Vibe Coding
  • Muscle Memory

Languages

  • Python
  • JavaScript
  • TypeScript
  • Rust
  • SQL
  • GDScript

Legal

  • Terms
  • Privacy
  • Contact

© 2026 Syntax Cache

Cancel anytime in 2 clicks. Keep access until the end of your billing period.

No refunds for partial billing periods.