---
title: "24.1 Declarative macros: macro_rules!"
description: "Code that writes code: how macro_rules! matches patterns and expands into source, paying off the println! mystery from lesson 1.1."
url: "https://learnrust.net/chapter-24/declarative-macros/"
last_updated: "2026-06-13"
---

# 24.1 Declarative macros: macro_rules!

Back in lesson [1.1](https://learnrust.net/chapter-1/statements-and-program-structure/), in your very first program, you wrote `println!` and we told you the `!` marks a **macro**, "a supercharged function," with the full story promised much later. This is much later. A macro is **code that writes code**: at compile time, before your program is built, a macro takes the source you wrote and *expands* it into more source, which the compiler then compiles as if you'd typed it yourself. That's the supercharging. This lesson explains the most common kind, the **declarative macro** written with `macro_rules!`, and at last `println!` stops being magic.

## Why macros exist: what functions can't do

You might reasonably ask why we need macros at all when we have functions. Functions are wonderful, but they have fixed shapes that the language won't let them break. A function takes a *fixed number* of arguments of *fixed types*. Yet `println!` accepts any number of arguments of any types: `println!("{}", x)`, `println!("{} and {}", a, b)`, `println!("hi")`. No Rust *function* can do that, the signature would have to commit to an argument count. `vec![1, 2, 3]` builds a vector from however many elements you list; `vec![0; 100]` builds a hundred zeros. That flexible, variable syntax is precisely what a function can't express and a macro can.

A macro operates a level up: instead of taking *values* at run time, it takes *syntax* at compile time and rewrites it. `println!("{}", x)` expands, before compilation, into the actual buffer-writing code, with the right number of arguments wired in. The `!` is your signal that you're calling a macro, not a function, and so the usual function rules don't apply: variable argument counts, the trailing `;` flexibility, all of it comes from the expansion happening at compile time.

## A first macro_rules!

Here's a macro that does nothing useful but shows the wiring, it expands into a `println!`:

```rust
macro_rules! say_hello {
    () => {
        println!("Hello!");
    };
}

fn main() {
    say_hello!();
}
```

```
Hello!
```

Read the structure. `macro_rules! say_hello` defines a macro named `say_hello`. Inside is one **rule**: a *matcher* on the left of `=>` and an *expansion* (the "transcriber") on the right. The matcher `()` means "this rule matches when the macro is called with empty parentheses." The expansion `{ println!("Hello!"); }` is the code that replaces the call. So when the compiler sees `say_hello!()`, it substitutes the expansion in its place, exactly as if you'd written `println!("Hello!");` there. A macro is a set of pattern-matching rules: *match this syntax, replace it with that syntax*.

## Capturing input

Empty parentheses aren't interesting. The power is in matching *parts* of the input and reusing them in the expansion. You capture a piece of syntax into a **metavariable**, written `$name:kind`, where `kind` says what sort of syntax to match. The most common kind is `expr`, an expression:

```rust
macro_rules! print_twice {
    ($x:expr) => {
        println!("{}", $x);
        println!("{}", $x);
    };
}

fn main() {
    print_twice!(5 + 5);
}
```

```
10
10
```

The matcher `($x:expr)` says "match one expression and call it `$x`." We called `print_twice!(5 + 5)`, so `$x` captures the expression `5 + 5`, and the expansion drops `$x` into both `println!`s. The macro expands into two prints of `5 + 5`. Notice `$x` is *syntax*, the expression `5 + 5`, substituted into the code, not the value `10` passed at run time. That's the whole difference between a macro and a function: the macro pastes your code in, and the compiler evaluates the result.

Other useful kinds you'll see: `ident` (an identifier, like a variable or function name), `ty` (a type), `literal` (a literal value), `block` (a `{ ... }` block), `tt` (a single "token tree," the most flexible). You pick the kind that matches the syntax you want to capture.

## Repetition: matching "any number of"

Now the feature that makes `vec!` and `println!` possible: matching a *variable number* of things. Inside a matcher, `$( ... ),*` means "match the pattern inside, repeated, separated by commas, zero or more times." The same `$( ... ),*` in the expansion repeats the output once per captured item. Here's a simplified `vec!`:

```rust
macro_rules! my_vec {
    ( $( $x:expr ),* ) => {
        {
            let mut temp = Vec::new();
            $(
                temp.push($x);
            )*
            temp
        }
    };
}

fn main() {
    let v = my_vec![1, 2, 3];
    println!("{v:?}");
}
```

```
[1, 2, 3]
```

Trace it. The matcher `$( $x:expr ),*` captures a comma-separated list of expressions, here `1`, `2`, `3`. The expansion builds a vector and, for *each* captured `$x`, emits a `temp.push($x)`, that's what the `$( temp.push($x); )*` does. So `my_vec![1, 2, 3]` expands, at compile time, into roughly:

```rust
{
    let mut temp = Vec::new();
    temp.push(1);
    temp.push(2);
    temp.push(3);
    temp
}
```

which evaluates to the vector. *That* is how the real `vec!` works, and why it accepts any number of elements where a function couldn't. The macro generated one `push` per element by repeating its body, at compile time, before the compiler ever saw the code. (This pays off lesson [1.1](https://learnrust.net/chapter-1/statements-and-program-structure/)'s promise from twenty-three chapters back: the `!` means "this is a macro, expanded into code," and `println!`/`vec!` are exactly these pattern-and-repetition rules, just more elaborate.)

> **Key insight**
>
> A `macro_rules!` macro is a compile-time find-and-replace driven by syntax patterns. It matches the shape of the code you wrote (`$x:expr`, repeated lists via `$(...),*`) and substitutes it into a template that becomes real source code. This is why macros can do things functions can't, variable argument counts, generating repeated code, building new syntax: they run *before* compilation and produce code, rather than running *during* execution and producing values. The `!` you've typed since lesson 1.1 was always announcing "this gets expanded, not called."

## A word on the rough edges

Honesty, as with async: `macro_rules!` is powerful but its syntax is genuinely cryptic, the `$`, the `:expr`, the `$(...),*` are a small language unto themselves, and macro error messages are often worse than ordinary ones, because they can point at *generated* code you never wrote. Writing macros is a more advanced skill than writing functions, and you'll *use* hundreds of macros for every one you write. The next lesson is specifically about that judgment: when a macro is the right tool, and when a function or generic was the answer all along.

## Quiz time

**Question #1**

What does a macro do, and how is that different from what a function does?

<details class="solution">
<summary>Show solution</summary>

A macro is code that writes code: at *compile time*, it takes the syntax you wrote and expands it into more source code, which the compiler then compiles. A function runs at *run time* and operates on values. The difference is the level and timing: a macro manipulates syntax before compilation (so it can do things like accept a variable number of arguments and generate repeated code), while a function takes a fixed set of value arguments during execution.

</details>

**Question #2**

Why can `println!` and `vec!` accept any number of arguments when an ordinary function can't?

<details class="solution">
<summary>Show solution</summary>

Because they're macros, not functions. A function's signature commits to a fixed number and type of parameters. A macro matches *syntax* with patterns, including repetition patterns like `$( $x:expr ),*` that match "zero or more comma-separated expressions," and expands its body once per captured item. So the macro generates the right code for however many arguments you pass, something a fixed function signature cannot express.

</details>

**Question #3**

In `macro_rules!`, what does `$x:expr` capture, and is `$x` a value or syntax?

<details class="solution">
<summary>Show solution</summary>

`$x:expr` captures one *expression* from the macro's input into the metavariable `$x` (the `:expr` is the fragment kind, here "expression"). `$x` is *syntax*, not a runtime value: the macro substitutes the captured expression into the expansion's code as-is, and the compiler evaluates the result. So `print_twice!(5 + 5)` pastes `5 + 5` into the generated code, it doesn't pass the value `10`.

</details>

You can read and write a basic declarative macro now. But *should* you? The next lesson ([24.2](https://learnrust.net/chapter-24/when-to-write-a-macro/)) is about that judgment: the cases where a macro earns its complexity, and the more common cases where a function or generic is the better answer.

## Sitemap

See the full [sitemap](https://learnrust.net/sitemap.md) for all pages.
