Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.syntblaze.com/llms.txt

Use this file to discover all available pages before exploring further.

Declarative macros in Rust, defined via the macro_rules! construct, are a metaprogramming feature that allows developers to write code that generates other Rust code at compile time. They operate by matching an input stream of tokens against predefined syntactic patterns and expanding them into an output token stream (Abstract Syntax Tree fragments) before the compiler performs type checking or borrow checking.

Core Syntax and Structure

Declarative macros function similarly to a match expression, but instead of matching runtime values, they match compile-time token streams.
macro_rules! macro_name {
    (pattern1) => { expansion_code1 };
    (pattern2) => { expansion_code2 };
}
When a macro is invoked, the compiler evaluates the input tokens against each pattern sequentially. The first matching pattern triggers its corresponding expansion block.

Metavariables and Fragment Specifiers

To capture parts of the input token stream, declarative macros use metavariables, prefixed with a dollar sign ($). Every metavariable must be paired with a fragment specifier (or designator) that dictates what kind of Rust syntax it is allowed to match. The syntax for capturing a token is $name:specifier. Common fragment specifiers include:
  • expr: An expression (e.g., 2 + 2, "string", function_call()).
  • ident: An identifier (e.g., variable names, function names).
  • ty: A type (e.g., i32, Vec<String>).
  • stmt: A statement (e.g., let x = 5;).
  • path: A module path (e.g., std::collections::HashMap).
  • tt: A single token tree (matches almost any single syntactic element or matched pair of brackets).
macro_rules! bind_value {
    // Captures an identifier ($var) and an expression ($val)
    ($var:ident := $val:expr) => {
        let $var = $val;
    };
}

// Invocation: bind_value!(x := 10 + 20);
// Expands to: let x = 10 + 20;

Repetition

Declarative macros can handle an arbitrary number of input tokens using repetition operators. The repetition syntax is $(pattern)separator quantifier.
  • $( ... ): The block containing the pattern to be repeated.
  • separator: An optional punctuation mark separating the repetitions (commonly , or ;).
  • quantifier: Dictates the repetition count.
    • *: Zero or more times.
    • +: One or more times.
    • ?: Zero or one time (optional).
When expanding repeated metavariables, the expansion block must use a repetition block $( ... ) followed by a quantifier that matches the repetition depth of the captured tokens. However, the separator used in the expansion does not need to match the separator used in the pattern. For example, a macro can match tokens using a comma $( ... ),* and expand them using a semicolon $( ... );*, or with no separator at all $( ... )*.

Expansion Contexts

A macro’s expansion is parsed according to the syntactic context in which it is invoked: as an expression, a pattern, a type, an item, or a statement. When invoked in a statement context (such as inside a function body), a macro’s expansion is parsed as a sequence of statements. A single macro invocation can seamlessly expand to multiple independent statements in a flat block without needing to wrap them in an inner scope.
macro_rules! declare_multiple {
    // Matches zero or more comma-separated identifier/type pairs
    ( $( $name:ident : $type:ty ),* ) => {
        // Expands directly to multiple independent statements
        $(
            let $name: $type = Default::default();
        )*
    };
}

// Invocation: declare_multiple!(a: i32, b: String);
// Expands to:
// let a: i32 = Default::default();
// let b: String = Default::default();

Macro Hygiene

Rust’s declarative macros are partially hygienic. This means that local variables declared inside the macro’s expansion block will not conflict with local variables in the surrounding code where the macro is invoked. The compiler assigns distinct syntax contexts to them. However, this hygiene only applies to local variables and labels. It does not apply to items like functions, traits, or types. If a macro defines a new function, that function is injected directly into the caller’s scope and can cause naming collisions if not carefully managed.

Scoping and Exporting

Unlike standard Rust items (functions, structs) which follow module-based visibility rules (pub), declarative macros are scoped lexically by default. A macro is only available in the code that follows its definition. To make a declarative macro available outside its defining module, it must be annotated with the #[macro_export] attribute. This hoists the macro to the root of the crate, making it accessible globally within the crate and to external crates that depend on it.
#[macro_export]
macro_rules! public_macro {
    () => { ... };
}
Master Rust with Deep Grasping Methodology!Learn More