← Home ← Codex ← DEBT
Browse by Category
+ added · updated 7d
← Back to glossary

Rust Macro System

rust Advanced
debt(d7/e5/b5/t7)
d7 Detectability Operational debt — how invisible misuse is to your safety net

Closest to 'only careful code review or runtime testing' (d7). detection_hints.automated is no, with only a code_pattern regex for macro_rules!/#[proc_macro]; the compiler catches syntactic errors but the misuse here — reaching for a macro where a generic would do, or hygiene/fragment-specifier confusion — is invisible to automated tools and surfaces only in cryptic expansion failures during review or compile.

e5 Effort Remediation debt — work required to fix once spotted

Closest to 'significant refactor in one component' (e5). The quick_fix is to prefer generics/traits/const functions and move proc macros into a dedicated proc-macro crate — replacing a macro with a generic or extracting a separate crate touches multiple files and reshapes a component, not a one-line swap.

b5 Burden Structural debt — long-term weight of choosing wrong

Closest to 'persistent productivity tax' (b5). applies_to spans cli, library, web, and queue-worker; a macro is code-generation that is opaque to tooling and hard to debug, slowing compile times and many work streams that depend on the generated code, though it stops short of defining the whole system's shape.

t7 Trap Cognitive debt — how counter-intuitive correct behaviour is

Closest to 'serious trap' (t7). The misconception is that Rust macros are C-preprocessor-style textual find-and-replace that capture/shadow call-site variables; in reality they are hygienic and operate on typed token streams — this directly contradicts how a developer coming from C expects a similar concept to behave, a serious cross-language trap.

About DEBT scoring →

Also Known As

macro_rules proc macros derive macro declarative macros

TL;DR

Rust macros generate code at compile time through declarative pattern matching or procedural token manipulation, not runtime text substitution.

Explanation

Rust has two distinct macro systems that both run during compilation, expanding into ordinary Rust code before type checking and code generation. They are nothing like C's textual preprocessor: Rust macros operate on token streams and respect the language's syntax and hygiene rules, so they cannot silently capture or clobber identifiers from the call site.

Declarative macros, written with `macro_rules!`, match against patterns of tokens and emit code per matching arm. They use fragment specifiers such as `$x:expr`, `$t:ty`, `$i:ident`, and `$p:pat` to capture syntactic categories, and repetition operators like `$( ... )*` and `$( ... ),+` to handle variadic input. The standard library's `vec!`, `println!`, and `assert_eq!` are declarative macros. They are powerful for reducing boilerplate but are limited to pattern-driven expansion and can produce confusing errors when input does not match any arm.

Procedural macros are functions that take a `TokenStream` and return a `TokenStream`, giving full programmatic control over generated code. They come in three forms: derive macros (`#[derive(Serialize)]`), attribute macros (`#[route(GET, "/")]`), and function-like macros (`sql!(...)`). Procedural macros must live in their own crate marked with `proc-macro = true`, and the ecosystem relies on the `syn` crate to parse tokens into an AST and `quote` to build output, with `proc-macro2` bridging the types.

Hygiene is the feature that separates Rust macros from naive substitution. Identifiers introduced by a macro live in their own syntactic context, so a temporary variable a macro creates will not collide with a variable of the same name at the call site. This makes macros composable and safe in ways textual macros never are.

Macros are the right tool when you need code that varies in structure - implementing a trait for many types, building a domain-specific syntax, or eliminating repetitive impl blocks. They are the wrong tool when a generic function, trait, or const would do, because macros are harder to read, harder to debug (errors point at expanded code), slower to compile, and invisible to many IDE features. Reach for the simplest abstraction first and escalate to macros only when ordinary language features cannot express the pattern.

Common Misconception

Rust macros are textual find-and-replace like the C preprocessor and can leak or capture variables from the surrounding scope. In reality they operate on typed token streams with hygiene, so macro-introduced identifiers cannot accidentally collide with call-site names.

Why It Matters

Reaching for macros where a generic or trait would suffice produces code that is slow to compile, hard to debug, and opaque to tooling, while misunderstanding hygiene leads to surprising expansion failures.

Common Mistakes

  • Writing a macro_rules! macro for something a generic function or trait could express, adding complexity for no real benefit.
  • Assuming macros substitute text like the C preprocessor and expecting them to capture or shadow call-site variables.
  • Forgetting that procedural macros must live in a separate crate with proc-macro = true in Cargo.toml.
  • Choosing the wrong fragment specifier (for example $x:expr where $x:ty is needed) and getting cryptic match failures.
  • Mishandling repetition syntax so $( ... )* expands with the wrong separators or arity.

Avoid When

  • A generic function, trait, or const can express the same behavior with clearer errors and better tooling support.
  • The abstraction is needed only once and the macro adds indirection that obscures rather than clarifies the code.
  • You are duplicating logic that could be shared through ordinary composition, since macro-generated code is harder to debug.
  • Compile time and readability matter more than saving a few lines, because macros expand slowly and confuse IDEs.

When To Use

  • Generating repetitive trait implementations across many types where generics cannot abstract the variation.
  • Building variadic or DSL-like syntax such as vec!, format strings, or query builders.
  • Deriving boilerplate like serialization or builders via derive macros instead of hand-writing each impl.
  • Eliminating structural duplication that no function, generic, or const expression can capture.

Code Examples

✗ Vulnerable
// Using a macro to do what a generic function does cleanly.
macro_rules! max_of {
    ($a:expr, $b:expr) => {
        // Evaluates each argument twice: side effects run twice!
        if $a > $b { $a } else { $b }
    };
}

fn next() -> i32 {
    println!("called");
    5
}

fn main() {
    // next() is invoked twice because the macro inlines the expression.
    let m = max_of!(next(), 3);
    println!("{}", m);
}
✓ Fixed
// A generic function: arguments evaluated once, clear errors, IDE-friendly.
fn max_of<T: PartialOrd>(a: T, b: T) -> T {
    if a > b { a } else { b }
}

// Reserve macros for what functions cannot do, e.g. variadic construction.
macro_rules! string_vec {
    ( $( $item:expr ),* $(,)? ) => {
        vec![ $( $item.to_string() ),* ]
    };
}

fn next() -> i32 {
    println!("called");
    5
}

fn main() {
    // next() runs exactly once.
    let m = max_of(next(), 3);
    println!("{}", m);

    let names = string_vec!["a", "b", "c"];
    println!("{:?}", names);
}

Added 6 Jun 2026
Views 5
Rate this term
No ratings yet
🤖 AI Guestbook educational data only
| |
Last 30 days
0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 0 pings S 0 pings S 0 pings M 0 pings T 0 pings W 0 pings T 0 pings F 1 ping S 3 pings S 0 pings M 0 pings T 0 pings W
No pings yet today
No pings yesterday
Google 2 Scrapy 1 Amazonbot 1
crawler 4
DEV INTEL Tools & Severity
🟢 Low ⚙ Fix effort: Medium
⚡ Quick Fix
Prefer generics, traits, or const functions first; use macro_rules! only for variadic or structural code, and put procedural macros in a dedicated proc-macro crate with syn and quote.
📦 Applies To
cli library web queue-worker syn quote
🔗 Prerequisites
🔍 Detection Hints
macro_rules!\s+\w+|#\[proc_macro
Auto-detectable: ✗ No
⚠ Related Problems
🤖 AI Agent
Confidence: Low False Positives: High ✗ Manual fix Fix: High Context: File Tests: Update

✓ schema.org compliant