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

  • playground

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 expands 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 expanded can be used to show the preprocessed program as text

Repetition and Condition

  • Powerful, but easy to get wrong

  • playground

  • Varargs is 70% of the reason for Rust macros

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.

Scope Stuff

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

    playground

  • Rust 2015 Edition:

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

    • Making macros from another crate visible requires #[macro_use] per crate

  • Rust 2018 Edition:

    • Macro visibility is controlled by normal import rules

A Practical Example

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

Last modified: Wednesday, 7 August 2019, 3:20 AM