Idea as old as computing; map from text to text using textual "functions"
Famous macro systems: LISP, M4, TeX, CPP, Scheme
Often a "preprocessor" like CPP: source-to-source
Modern macro systems are a bit lower-level
Mappings from parsed source tokens to source tokens
"Declarative", which use rules and matching: e.g.
"Procedural", which call Rust functions with token trees: e.g.
Book / I will only talk about declarative
Procedural is much easier than it used to be; see the documentation and examples online.
Introducing A Macro
macro_rules!itself looks / acts like a macro
Argument is a sequence of rules
Each rule has a LHS that is a token pattern to match, and a RHS that is tokens to rewrite using the match
Both sides are lexed by the compiler: you can't use arbitrary text
Rules Run In Order
The macro rules match from top to bottom. The first matching rule is chosen
A rule may suffer from type: the patterns match syntactically, but the pattern type is wrong. If this happens, compilation will fail right there
Double-expansion is dangerous, as with CPP
Macros are just tokenized, so weird errors (such as including the fragment type in the expansion) won't be caught at macro expansion time -- they will be caught at code compile time
Rules Can Be Recursive
Note that our
eprintln!. It can also expand itself, either directly or indirectly
There is an expansion recursion depth limit of 64 to prevent runaway macros from grinding up all the machine resources. The depth limit can be increased with
#![recursion_limit = "256"]or something similar
#![feature(trace_macros)]can be useful here for debugging expansions
Other Macro Debugging
log_syntax!()will print its arguments to the terminal at compile time
rustc -Z unstable-options --pretty expandedcan be used to show the preprocessed program as text
Repetition and Condition
Powerful, but easy to get wrong
Varargs is 70% of the reason for Rust macros
Lots of compiler builtins, e.g.
line!(). See the book for details
Lots of "fragment types", e.g.
ttfragment is special: it matches any "token tree" the Rust compiler can build. This is either a list of stuff inside some kind of outer brackets, or it's a single token of arbitrary kind.
Local variables and arguments created inside a macro "cannot escape": they are in a different namespace and thus "hygienic"
Rust 2015 Edition:
Making macros visible to another crate requires
Making macros from another crate visible requires
Rust 2018 Edition:
- Macro visibility is controlled by normal import rules