Rust has a macro system now!
Well, it has the beginning of a macro system. There are a few things that are glaringly absent:
- hygiene — It’s really nice to have hygiene, but the standard Dybvig algorithm for hygienic macro expansion was a bit too big to implement, and still have time for all the other things.
- macro scope / macro export — Currently, macros can only be used in the same file they were defined, and they don’t respect any kind of scope.
- AST agnosticism — The macro expander gives privileged status to expressions. But it would be nice to be able to abstract over other parts of the AST.
In spite of these things, a story for declarative macros in Rust has taken shape:
- Macro expansion has its own phase, and the compiler can emit expanded code, which can be useful for debugging.
- Macro expansion happens in the “correct” but unintuitive order: the outermost macro invocation is expanded first.
- Syntax stores a “backtrace” indicating the series of macro invocations that produced it. This has been useful for debugging errors in code produced by complex macros. However, it’s a bit of a hack. In particular, doing the Right Thing for macro-defining macros would require making the backtrace into a tree…
- You can use Macro By Example to define macros. It’s a simple, intuitive, declarative way to specify macros, borrowed from Scheme. Unfortunately, I can’t find any good introductions to it, so all I can suggest to the curious reader is that they search for “ellipses” in JRM’s Syntax-rules Primer for the Merely Eccentric, or reading the paper that codified the system: “Macro-by-example: Deriving syntactic transformations from their specifications”.
Complicated macro invocations in Rust currently look much like Scheme macros, if you turned all the parentheses into square brackets. We have some interesting ideas for how things could be different. But that’s for the future, and for now, I’ll sign off.