Search code examples
macrosrustmetaprogramminghygiene

Why does macro hygiene not prevent collisions between multiple const definitions?


I thought "hygiene" would prevent collisions between Xs defined within my macro m! but that turned out not to be the case. What am I misunderstanding?

macro_rules! m {
    ($e:expr) => {
        const X: i32 = $e;
    };
}

m!(0);
m!(1);

fn main() {
    m!(2);
    m!(3);
}

Playground

Error message:

error[E0428]: the name `X` is defined multiple times
 --> src/main.rs:3:9
  |
3 |         const X: i32 = $e;
  |         ^^^^^^^^^^^^^^^^^^
  |         |
  |         `X` redefined here
  |         previous definition of the value `X` here
...
7 | m!(0);
  | ------ in this macro invocation
  |
  = note: `X` must be defined only once in the value namespace of this module

Solution

  • From The Rust Programming Language (first edition)'s section on macro hygiene:

    This [i.e. renaming] holds for let bindings and loop labels, but not for items

    The Rust reference defines items:

    An item is a component of a crate. Items are organized within a crate by a nested set of modules. Every crate has a single "outermost" anonymous module; all further items within the crate have paths within the module tree of the crate.

    Items are entirely determined at compile-time, generally remain fixed during execution, and may reside in read-only memory.

    There are several kinds of items:

    • modules
    • extern crate declarations
    • use declarations
    • function definitions
    • type definitions
    • struct definitions
    • enumeration definitions
    • union definitions
    • constant items
    • static items
    • trait definitions
    • implementations
    • extern blocks

    This makes sense: if you you introduce an item in a macro you presumably want to actually use it from other items/modules/crates (and thus outside the macro), but you can't if you don't know its name, so the compiler can't rename it.