Course contentsShow
← Rust
Lesson 1513 of 1,91013. Metaprogramming with MacrosFree lesson

The Anatomy of macro_rules!

You will distinguish between standard functions and declarative macros using pattern matching and token trees.

In this lesson, you will learn how macro_rules! leverages pattern matching to transform syntax, effectively treating your code as data long before the compiler sees standard function calls.

Beyond the Function Call

In standard Rust, a function is a discrete unit of logic defined by its signature and body; it operates on runtime values. A declarative macro, defined via macro_rules!, operates at a higher abstraction layer: the Token Tree. While functions receive evaluated arguments, macros receive raw tokens—the skeletal structure of your code—and apply structural pattern matching to decide how to expand them.

Pattern Matching on Syntax

Think of macro_rules! as a compiler-integrated text transformer that understands Rust's grammar. You provide a set of match arms where the "input" is a pattern of tokens, and the "output" is the replacement code.

macro_rules! log_register {
    ($reg:ident) => {
        println!("Accessing register: {}", stringify!($reg));
    };
}

In the example above, $reg:ident is a metavariable with a specific fragment specifier (ident). When you call log_register!(STATUS), the macro captures the identifier STATUS as a token tree and injects it into the stringify! call.

Why Token Trees Matter

Unlike a simple regex or text replacement, the compiler parses your macro patterns into token trees. This means the compiler understands the nested structure of your code (braces, brackets, and parentheses). You are not just concatenating strings; you are manipulating a formal tree of syntax.

The core difference is timing and perspective:

  1. Functions: Operate on values at runtime; the compiler has already validated the structure of the call site.
  2. Macros: Operate on tokens at compile time; you are effectively writing a program that writes another program, allowing for powerful code generation that avoids the boilerplate inherent in embedded development, such as repetitive memory-mapped I/O registration or status checks.

Because macros process tokens before type checking, they can introduce code that would otherwise be syntactically impossible to express in a standard function—such as variadic arguments or complex domain-specific languages within your codebase.

Key Takeaway: Declarative macros use pattern matching on token trees to transform code structures at compile time, providing a level of syntactical flexibility that functions cannot achieve.