Macros Best Practices
Prefer functions, generics, and traits; reach for macros when syntax or codegen demands it. Good macros have great errors, tests, and documented input grammar.
Search across all documentation pages
Prefer functions, generics, and traits; reach for macros when syntax or codegen demands it. Good macros have great errors, tests, and documented input grammar.
macro_rules! or proc-macro crate.fn, const fn, trait, impl first. Better errors and IDE support.macro_rules! for repetitive syntax only. Not for business logic.proc-macro = true crate. Keep syn off runtime dependency graph.syn::Error::new_spanned for diagnostics. Point at user's token, not macro internals.split_for_impl. Derives must work on struct Foo<T>.#[allow] or pub.$crate:: paths in exported macro_rules!. Correct resolver from consumer crate.cargo expand snapshots for non-trivial macros. Checked in CI via macrotest or manual review.trybuild compile-fail tests for invalid input. Lock error messages.proc_macro2. Without full compiler driver.eprintln! debug behind feature or env. No noisy builds for users.build.rs if needed.syn/quote versions consciously. Workspace alignment reduces duplicate syn.unsafe in generated code like hand-written unsafe. Macro does not absolve review.Vec-like literals, small assert helpers, repetitive match arms - not whole ORMs.
Usually separate publishable macro crate even if internal monorepo member.
As needed; keep related derives together; split if compile time hurts consumers.
Prefer safe expansion; if unsafe required, document invariants in emitted docs.
Yes for frameworks; still offer non-macro builder path when feasible for IDE users.
Path resolution and use preludes; test expand under 2024 consumer crate.
Workshop: expand serde derive, write tiny derive, debug with trybuild.
Plan escape hatch: generated code pattern stable enough to hand-write if removing macro.
Clippy lints apply to expansion; fix generator not allow everywhere.
Show expansion examples in docs; doc_cfg for feature-gated macro paths.
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.
Reviewed by Chris St. John·Last updated Jul 16, 2026