A function-like macro in Rust is a metaprogramming construct invoked using the macro invocation operator (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.
!) that accepts a stream of tokens as input and expands into a new stream of tokens at compile time. Unlike standard functions, which operate on evaluated values at runtime, function-like macros operate on Token Streams (or Token Trees) during the parsing phase, before the full Abstract Syntax Tree (AST) is constructed. This enables syntactic abstraction and arbitrary code generation.
Rust provides two distinct mechanisms for defining function-like macros: Declarative Macros and Procedural Macros.
Declarative Macros (macro_rules!)
Declarative macros are defined using the built-in macro_rules! construct. They operate via a pattern-matching engine that evaluates the input token stream against a series of defined rules. When a match is found, the macro expands according to the corresponding transcriber block.
Syntax Visualization:
- Metavariables: Prefixed with
$, these capture matched tokens from the input stream. - Fragment Specifiers: Define the expected syntax node type for the metavariable (e.g.,
exprfor expressions,identfor identifiers,tyfor types,ttfor token trees). - Repetitions: Indicated by syntax like
$( ... ),*, allowing the macro to accept and expand variable-length lists of tokens.
Macro Hygiene
A fundamental feature that distinguishes Rust’s declarative macros from C-style text replacement is macro hygiene. Declarative macros are partially hygienic. This means that local variables, labels, and other identifiers declared within the macro’s transcriber block (such astemp_vec in the example above) are resolved in the macro’s definition context, not the caller’s context. This protects the macro’s internal variables from accidentally clashing with variables in the scope where the macro is invoked.
Note: Hygiene in declarative macros applies primarily to local variables and labels. Items like functions or types are resolved in the call site’s context. Procedural macros, by contrast, are unhygienic by default, requiring manual hygiene management using the Span API.
Scoping and Visibility
Because declarative macros operate differently from standard functions, they do not use thepub keyword for visibility. Instead, declarative macros use the #[macro_export] attribute to be visible outside their defining module. Applying #[macro_export] hoists the macro into the root scope of the crate, making it accessible to external crates. Without this attribute, a declarative macro is only visible within the module where it is defined and its submodules.
Procedural Function-like Macros
Procedural function-like macros are defined as public functions within a specialized crate type (proc-macro = true). They are written in standard Rust and execute as compiler plugins at compile time to transform an input TokenStream into an output TokenStream.
Syntax Visualization:
TokenStream, allowing for arbitrary programmatic inspection and manipulation before emitting the final TokenStream.
Invocation Syntax
Regardless of whether a function-like macro is declarative or procedural, it is invoked by appending a bang (!) to its identifier. The input token stream can be enclosed in parentheses (), brackets [], or braces {}. The compiler treats these delimiters identically for the purpose of macro expansion, though convention dictates their usage based on the macro’s structural output.
Expansion Mechanics
- Lexing: The compiler converts the source code into a stream of raw tokens.
- Token Tree Parsing: The compiler groups these raw tokens into token trees (
tt) by matching delimiters ((),[], and{}). - Invocation Parsing: The compiler encounters the
!operator and identifies the macro invocation. - Expansion: The macro consumes the token stream within its delimiters. For declarative macros, the compiler evaluates the matchers sequentially. For procedural macros, the compiler executes the compiled macro binary, passing the tokens as arguments.
- Integration: The resulting
TokenStreamis parsed and integrated into the AST, replacing the original macro invocation. The expanded code must form valid Rust syntax for the specific context in which it was invoked (e.g., an expression context, an item context, or a statement context).
Master Rust with Deep Grasping Methodology!Learn More





