macro_rules! in Rust: Metavariables & vs Proc-Macro

Max WellsMax WellsFounder of Rustify

TL;DR: macro_rules! defines declarative (pattern-matching) macros in Rust. You define a set of rules: each rule matches a pattern of tokens and expands to replacement code. They are used for reducing boilerplate, creating DSLs, and writing variadic functions (functions with variable numbers of arguments). vec![1, 2, 3], println!, and assert! are all macro_rules! macros. They compile faster and are simpler than proc-macros, but cannot inspect types or generate complex impls.


How Do You Define a macro_rules! Macro?

A macro rule has a pattern (matcher) and an expansion (transcriber), separated by =>. Since Rust 2018 Edition, macro_rules! macros can be imported with use rather than relying on #[macro_use].

macro_rules! say_hello {
    () => {
        println!("Hello!");
    };
    ($name:expr) => {
        println!("Hello, {}!", $name);
    };
}
 
say_hello!();           // Hello!
say_hello!("Rustacean"); // Hello, Rustacean!

Each arm is tried in order. The first matching pattern wins. $name:expr is a metavariable that captures an expression.


What Are the Common Metavariable Types?

Metavariable designators specify what kind of syntax each capture accepts.

DesignatorMatches
exprAny expression
identAn identifier (variable/function name)
tyA type
patA pattern
stmtA statement
blockA { ... } block
literalA literal value
ttA single token tree (most flexible)

Use tt when you need to match arbitrary syntax that doesn't fit a more specific designator.


How Do You Write Variadic Macros?

Use $(...)* or $(...)+ to repeat a pattern zero-or-more or one-or-more times.

macro_rules! sum {
    ($($x:expr),*) => {{
        let mut total = 0;
        $(total += $x;)*
        total
    }};
}
 
let result = sum!(1, 2, 3, 4, 5); // 15

$($x:expr),* captures a comma-separated list of expressions into $x. The $(total += $x;)* expands one statement per captured value.


macro_rules! vs Proc-Macro in 2026

macro_rules! is pattern matching on tokens; proc-macros are full Rust programs that transform token streams; and in 2026 both remain essential, but for different jobs.

macro_rules!Proc-macro
SyntaxPattern/templateRust code with syn/quote
Type inspection
Separate crate required
Compile timeFastSlower
MaintainerRust compiler team (built-in)Community (syn, quote)
Best forRepetition, DSLs, variadicDerive impls, complex codegen
Error messagesConfusing (expansion site)Better with proc-macro-error

If you need to reduce boilerplate for simple repetition or write a small DSL inside your crate, choose macro_rules!. If you need to inspect struct fields, generate trait implementations from attributes, or produce derives like #[derive(Serialize)], choose a proc-macro; the extra compile cost is worth the power.


Frequently Asked Questions

Add #[macro_export] above the macro_rules! definition. This places it at the crate root so consumers can use it with use my_crate::my_macro.

{{ }} inside a macro expansion creates a block expression. The outer braces are part of the macro syntax; the inner braces are the Rust block. Use it when your expansion needs to be a single expression.

Macro expansion happens before type-checking, so errors point to the expanded code rather than the macro call site. Use cargo expand (via cargo-expand) to see the generated code and debug.

Yes. A macro_rules! macro can call itself in its expansion. This is commonly used to peel off one element at a time from a list, enabling variadic behavior. Be careful about hitting the default recursion limit (128); increase it with #![recursion_limit = "256"] at the crate root if needed.

Yes; Rust's declarative macros are hygienic by default. Variables introduced inside a macro expansion do not leak into the caller's scope, preventing accidental name collisions. This is a key advantage over C preprocessor macros.


Sources


  • macro: Overview of all macro types in Rust
  • proc-macro: Procedural macros for more complex code generation
  • derive: #[derive(...)] is powered by proc-macros
  • impl-trait: Return-position impl Trait can replace some macro patterns
  • generic: Generics and macros both reduce boilerplate; know when to use each

Keep Reading

Ready to Land a $80-120k Rust Job?