Macros

Hygiene

Macro hygiene is a compiler property that prevents local variable identifiers introduced inside a macro's body from accidentally colliding with—or shadowing—variables of the same name at the macro's call site.

How Span Contexts Prevent Identifier Collisions

Unlike C preprocessor #define macros (which perform raw text substitution and silently clobber caller variables that happen to share the same name), Rust attaches a syntax context (part of the token's Span) to every identifier:

  • An identifier written literally inside the macro transcriber (let temp = ...;) belongs to the macro's own hygiene context (think of it as having an invisible color distinct from the caller's scope).
  • An expression $e:expr passed by the caller keeps the caller's hygiene context.
Caller Scope (Blue Context):        Macro Body (Green Context):
  let temp = 100;                     let temp = 2;
  scale!(temp)         ───────────>   temp (Green: 2) * temp (Blue: 100) = 200
macro_rules! double_with_temp {
    ($user_expr:expr) => {{
        // This `temp` lives in the macro's hygiene context
        // and does NOT shadow the caller's `temp`!
        let temp = 2;
        $user_expr * temp
    }};
}

fn main() {
    let temp = 21;
    let result = double_with_temp!(temp);
    println!("Caller temp = {temp}, result = {result}");
}
Caller temp = 21, result = 42

Cannot Reference Macro-Internal Variables From Caller Code (E0425)

Similarly, a statement macro that declares a local binding let x = 10; using a literal identifier x inside the macro body does not expose x to subsequent statements at the call site:

// ❌ does not compile
macro_rules! make_hidden_x {
    () => {
        let x = 42;
    };
}

fn main() {
    make_hidden_x!();
    println!("{x}"); // Error: `x` in `main` has a different hygiene context than `x` in the macro
}
error[E0425]: cannot find value `x` in this scope
  --> src/main.rs:10:16
   |
2  | macro_rules! make_hidden_x {
   | -------------------------- similarly named local `x` defined here
...
10 |     println!("{x}");
   |                ^ help: a local variable with a similar name exists (notice the hygiene): `x`

Intentionally Binding a Caller Identifier via $var:ident

When a macro wants to introduce a variable or function into the caller's scope, the caller must pass the identifier name explicitly as an $id:ident parameter so the identifier carries the caller's hygiene context:

macro_rules! make_caller_var {
    ($var_name:ident = $val:expr) => {
        let $var_name = $val;
    };
}

fn main() {
    make_caller_var!(answer = 42);
    println!("Caller can read `answer`: {answer}");
}
Caller can read `answer`: 42

Scope of Hygiene in macro_rules! (Mixed-Site Hygiene)

In macro_rules!, hygiene is mixed-site:

  • Local variables and labels ('a, let x) are fully hygienic (def-site scoped).
  • Type names, module paths, functions, and struct fields resolved without $crate are looked up at the call site unless prefixed with $crate:: (which always resolves to the crate that defined the macro).