## 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]

• Let's talk first about declarative macros.

## 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

• examples/debug-macro.rs

## 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. examples/square-macro.rs

• Macros are just tokenized, so weird errors in the macro rule bodies won't be caught at macro expansion time -- they will be caught at code compile time. examples/macro-body-bug.rs

## Macro Debugging

• log_syntax!() will print its arguments to the terminal at compile time

• rustc -Z unstable-options --pretty expanded or the cargo-expand program can be used to show the preprocessed program as text

## Repetition and Condition

• Powerful, but easy to get wrong. examples/debug-macro-rep.rs

• Varargs is 70% of the reason for Rust macros

## Rules Can Be Recursive

• Note that our debug! example expands eprintln!. It can also expand itself, either directly or indirectly. examples/macro-nargs.rs

• Note that this expansion is at compile time: the source code can get huge and take a long time to generate and compile

• There is an expansion recursion depth limit of 64 to prevent runaway macros from overrunning the compiler stack. The depth limit can be increased with #![recursion_limit = "256"] or something similar

• #![feature(trace_macros)] can be useful here for debugging expansions

## More Facilities

• Lots of compiler builtins, e.g. line!(). See the book for details

• Lots 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. examples/macro-tt.rs

## Scope Stuff

• Local variables and arguments created inside a macro "cannot escape": they are in a different namespace and thus "hygienic"

• This won't compile

macro_rules! make_point {
($x:expr,$y:expr, $t:ty) => (let x:$t = $x; let y:$t = \$y;)
}

fn main() {
make_point!(3, 2, u32);
println!("{} {}", x, y);
}

• Making macros visible to another crate requires #[macro_export] per-macro

• Macro import is controlled by normal module import rules

## This is a bit dated, but otherwise great: Little Book of Rust Macros

Last modified: Tuesday, 25 May 2021, 1:32 AM