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 really talk about declarative

Introducing A Macro

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

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"

    https://play.rust-lang.org/?gist=fd4d9bf7d7e44fde943c7eef2d5e0cd0

  • 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 pub rules

A Practical Example

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

Last modified: Wednesday, 29 May 2019, 11:50 PM