Rust's proc macros let us use Rust to write Rust. Sadly, they can be hard to develop and debug because they live in special "proc-macro = true"
projects. Happily, crates such as proc-macro2, syn, quote, trybuild,
and proc_macro_error
can fix the difficulty. With the right setup, you can even set interactive breakpoints and single step through your macro code.
This free article in Towards Data Science/Medium describes the setup I used while developing anyinput. It goes over what I learned with lots of examples and tips in the form of "rules".
I'd love to hear about other folk's experiences. What tips do you have for making macro programming more like regular programming?
-- Carl
p.s. The "Rules":
- Use a Rust workspace and proc_macro2 to develop, debug, and unit-test your macro in a normal (non-macro) project.
- Use syn, proc_macro2, and quote to convert freely among literal code, tokens, syntax trees, and strings.
- Create easily debuggable unit tests that report any differences between what your macro does and what you expect.
- Use AST Explorer and the syn documentation to understand Rust syntax trees. Destructure syntax trees with Rust’s pattern matching and struct/enum access.
- Construct syntax trees with parse_quote! and Rust’s struct update syntax.
- Use syn’s Fold Trait to recursively traverse, destructure, and construct syntax trees.
- Use proc_macro_error to return ergonomic and testable errors.
- Create integration tests. Include UI tests based on trybuild.
- Follow the rules of elegant Rust API design, especially, eating your own dogfood, using Clippy, and writing good documentation.