When to use function-like over declarative macros
There are two ways to write a macro that’s invoked like foo!( ... ):
- a declarative macro, written with
macro_rules!, and - a procedural function-like macro, the
#[proc_macro]kind you’ve been building.
They’re called identically, so from the caller’s side you often can’t tell which one you’re using —
vec! is declarative, sqlx::query! is
procedural, both look the same. The question for you, the
author, is which to write. Procedural macros are far more powerful, but that power isn’t free, so
the honest default is: reach for macro_rules! first, and only escalate when it can’t do the
job.
What macro_rules! is good at
macro_rules! is a pattern-matching templating engine. It matches the input against
fragment specifiers — $e:expr, $t:ty, $i:ident, $($xs:expr),* for repetition — and
expands a fixed template. For anything that’s “take some Rust fragments and stamp out a fixed shape
of code,” it’s the right tool, and it needs no extra crate, no syn, no compile step of its own:
#![allow(unused)]
fn main() {
macro_rules! my_vec {
($($x:expr),* $(,)?) => {{
let mut v = Vec::new();
$( v.push($x); )*
v
}};
}
}
That’s the whole comma-separated-list family — vec!, min!, hashset! — expressions in, a
template out. If macro_rules! can express it, prefer it.
What only a procedural macro can do
You need #[proc_macro] when the input or the work exceeds simple templating:
- Syntax that isn’t Rust fragments.
macro_rules!can only match things that fit a fragment specifier. The moment your DSL contains tokens that aren’t valid Rust expressions/types/etc. —<div>inhtml!,GET "/" => handlerin a router —macro_rules!can’t describe it, but a hand-writtenParseimpl can read any token at all. - Inspecting the input as data. Counting arguments and emitting the count, validating a format
or SQL string against a real schema, deriving identifiers from the input — that’s arbitrary Rust
code running at compile time.
macro_rules!only rearranges tokens; it can’t compute over them. - Real diagnostics. A procedural macro can emit a
syn::Errorspanned at the exact offending token (you did this in chapter 4).macro_rules!errors are notoriously hard to aim.
The cost is real: a separate proc-macro crate, a parser to write and maintain, and slower
compiles. So the rule of thumb is a ladder — climb only as high as you have to:
- A plain function if there’s no code generation at all.
macro_rules!if it’s token templating.- A procedural macro only when you genuinely need to parse custom syntax or compute over the input.
Exercise
The exercise for this section is located in 05_function_like/03_when_to_use
No macro to write this time — just judgement calls. For each scenario in the exercise, decide
whether a procedural function-like macro is warranted, or whether macro_rules! (or even a
plain function) would do. The test messages explain the reasoning for each.