Blog: The Case for Macros

Wherein we look at where and why we need macros.

4 Likes

Interesting take on macro's, though I don't quite agree with all of it.

Specifically, the distinction between build.rs/code generation on the one hand and macros on the other is what I don't think is accurate.
Since my Common Lisp days I've seen macros as a viable way to do multiple things, from code generation to complex calculations and even full-fledged DSL's, all resolved at compile time (technically macro expansion time).
And that's the thing: build.rs is one (unfortunately sometimes necessary) way to do code generation. But it does not equate to it, as macros are another viable way of generating code. Even macro_rules! macros do this, in a simple way i.e. mostly by cutting boilerplate.

As for tools to deal with macros, the main ones I remember from CL are macroexpand, macroexpand-1, and Emacs. The clisp REPL you mention in your article probably hooks into at least the former 2, and probably (at least optionally) into the latter.

Interactive macro expansion is an intriguing idea though!

2 Likes

Of course you can do code generation outside your build process, but that will lead to all sorts of problems if you forget to regenerate the code, also you either must check in your generated code to version control or require your co-developers to run another step to set up their workspace. Both options are less than ideal. So in what cases is this comparison inaccurate?

Also of course you can build DSLs with macros. You can also use code generation for that and use your DSL as input, but that requires custom DSL parsing, so macros have an obvious advantage here.

A small point:

macros can make code harder to understand (both for humans and computers, for example many clippy lints have an explicit check to only lint outside of macros)

I'm not sure this example really supports the case here. I presume at least one prominent reason not to lint macro output code is that the user of the macro can't do much to fix any warning. This does perhaps mean there should be a separate set of lints to be used when authoring a macro, but otherwise the warning is going to the wrong place.

That's the thing with macros as they exist in Rust though: they are expanded automatically by rustc. I think this happens incrementally, but even then there's not really anything one can do to "forget to regenerate code" - one simply starts a new build and cargo and rustc do the rest.
One could forget to write the macro invocation I suppose, but that's analogous to forgetting to write a fn call expression - there is no substitute for human-level programming (yet).

With build.rs on the other hand, since I've started using Rust a couple of years ago, I have had several build issues related to stale code generation artifacts, or a weird refusal to (re)generate them without doing a cargo clean (hence the "unfortunate" remark in my previous post).

Ultimately I think (and please correct me if I'm wrong!) that you see "code generation" primarily as "using build.rs" and aside from that there's macros. I would argue there that macros rather than build.rs should be seen as the preferred way to do code generation where and when possible because they tend to have less potential issues when doing the code generation.

1 Like