Macros
Macros
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
Rust Macros
Mappings from parsed source tokens to source tokens
Two kinds:
"Declarative", which use rules and matching: e.g.
println!()
"Procedural", which call Rust functions with token trees: e.g.
#derive
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 macroArgument 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
Macro Bugs
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
debug!
example expandseprintln!
. It can also expand itself, either directly or indirectlyThere 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 timerustc -Z unstable-options --pretty expanded
can 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
More Facilities
Lots of compiler builtins, e.g.
line!()
. See the book for detailsLots of "fragment types", e.g.
ident
,ty
,tt
A
tt
fragment 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.
Scope Stuff
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
#[macro_export]
per-macroMaking macros from another crate visible requires
#[macro_use]
per crate
Rust 2018 Edition:
- Macro visibility is controlled by normal import rules