From d3c5780e1366c23000e7be403e830800191cb35b Mon Sep 17 00:00:00 2001 From: Rodin Date: Thu, 30 Apr 2026 15:13:10 -0700 Subject: [PATCH] docs: macro patterns from rust-lang/rust 10 patterns, 519 lines. Full spec compliance. Patterns: macro_rules!, trailing comma, $crate hygiene, multiple arms, #[derive], attribute macros, type-level repetition, built-in macros, format macros, macro visibility/export. --- patterns/macros.md | 519 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 519 insertions(+) create mode 100644 patterns/macros.md diff --git a/patterns/macros.md b/patterns/macros.md new file mode 100644 index 0000000..caa1382 --- /dev/null +++ b/patterns/macros.md @@ -0,0 +1,519 @@ +# Rust Macro Patterns + +Patterns for declarative and procedural macros in Rust, extracted +from the standard library source. + +**Source:** [rust-lang/rust](https://github.com/rust-lang/rust) at commit +[`f53b654`](https://github.com/rust-lang/rust/tree/f53b654a8882fd5fc036c4ca7a4ff41ce32497a6) + +**Stats:** 607 macro_rules! definitions, 63 #[macro_export], +1,116 #[derive(...)] usages, 28,639 #[inline] annotations. + +--- + +## 1. macro_rules! for Repetitive Code + +### Source: + +[library/alloc/src/macros.rs](https://github.com/rust-lang/rust/blob/f53b654a8882fd5fc036c4ca7a4ff41ce32497a6/library/alloc/src/macros.rs) (vec! macro) + +607 macro_rules! definitions in the stdlib. + +```rust +#[macro_export] +macro_rules! vec { + () => ( $crate::vec::Vec::new() ); + ($elem:expr; $n:expr) => ( $crate::vec::from_elem($elem, $n) ); + ($($x:expr),+ $(,)?) => ( + $crate::boxed::box_assume_init_into_vec_unsafe( + $crate::intrinsics::write_box_via_move( + $crate::boxed::Box::new_uninit(), [$($x),+] + ) + ) + ); +} +``` + +### Why + +`macro_rules!` eliminates repetitive boilerplate that can't be +abstracted with generics or traits. The `vec!` macro lets you +write `vec![1, 2, 3]` instead of constructing and pushing manually. +Macros operate on syntax trees — they can generate code that +functions can't. + +### When to Use + +**Triggers:** +- Repetitive code that differs only in types or count +- Syntax sugar that makes common patterns readable +- Implementing traits for many types at once (numeric types) +- Generating test cases from a table + +**Example — before:** +```rust +// Implementing Add for 10 numeric types manually: +impl Add for u8 { type Output = u8; fn add(self, rhs: u8) -> u8 { ... } } +impl Add for u16 { type Output = u16; fn add(self, rhs: u16) -> u16 { ... } } +impl Add for u32 { type Output = u32; fn add(self, rhs: u32) -> u32 { ... } } +// ... 7 more identical implementations +``` + +**Example — after:** +```rust +macro_rules! impl_add { + ($($t:ty),*) => { + $( + impl Add for $t { + type Output = $t; + fn add(self, rhs: $t) -> $t { self + rhs } + } + )* + } +} + +impl_add!(u8, u16, u32, u64, u128, i8, i16, i32, i64, i128); +``` + +### When NOT to Use + +**Don't use this when:** +- A generic function or trait works (prefer generics) +- The macro is used in only 1-2 places (inline the code) +- The macro makes code harder to understand/debug +- Error messages from the macro would be confusing + +--- + +## 2. Trailing Comma Support ($( ,)?) + +### Source: + +Standard pattern in all stdlib macros: + +```rust +macro_rules! vec { + ($($x:expr),+ $(,)?) => { ... } + // ^^^^ optional trailing comma +} +``` + +### Why + +Without `$(,)?`, users get cryptic errors when they add a trailing +comma (common when formatting multi-line macro invocations). +Every public macro should accept trailing commas. + +### When to Use + +**Always.** Every macro with comma-separated arguments should +accept an optional trailing comma. + +**Example — before:** +```rust +macro_rules! my_macro { + ($($x:expr),+) => { ... } +} + +my_macro!(1, 2, 3,); // ERROR: no rules matched — confusing +``` + +**Example — after:** +```rust +macro_rules! my_macro { + ($($x:expr),+ $(,)?) => { ... } +} + +my_macro!(1, 2, 3,); // Works! +my_macro!(1, 2, 3); // Also works! +``` + +--- + +## 3. $crate for Hygiene + +### Source: + +All `#[macro_export]` macros in the stdlib use `$crate`: + +```rust +macro_rules! vec { + () => ( $crate::vec::Vec::new() ); + // ^^^^^^ hygienic path to the defining crate +} +``` + +### Why + +`$crate` resolves to the crate where the macro is defined, +regardless of where it's used. Without it, macros break when +the user has a different name for your crate or doesn't import +the right modules. + +### When to Use + +**Triggers:** +- Any `#[macro_export]` macro that references items from its own crate +- Macros that will be used in other crates + +**Example — before:** +```rust +#[macro_export] +macro_rules! create_thing { + () => { Thing::new() } + // BROKEN: if user doesn't have `Thing` in scope, this fails +} +``` + +**Example — after:** +```rust +#[macro_export] +macro_rules! create_thing { + () => { $crate::Thing::new() } + // Works regardless of user's imports — $crate is always valid +} +``` + +### When NOT to Use + +**Don't use this when:** +- The macro is not exported (crate-internal macros) +- You're referencing items from other crates (use full path) + +--- + +## 4. Multiple Match Arms (Overloaded Syntax) + +### Source: + +[library/core/src/macros/mod.rs](https://github.com/rust-lang/rust/blob/f53b654a8882fd5fc036c4ca7a4ff41ce32497a6/library/core/src/macros/mod.rs) (assert_eq!) + +```rust +macro_rules! assert_eq { + ($left:expr, $right:expr $(,)?) => { ... }; + ($left:expr, $right:expr, $($arg:tt)+) => { ... }; +} +``` + +### Why + +Multiple arms let one macro serve different use cases. `assert_eq!` +works both with and without a custom message. The most specific +arm should come first (Rust matches top-to-bottom). + +### When to Use + +**Triggers:** +- A macro should work with different numbers of arguments +- You want syntactic variants (with/without options) +- Default behavior when optional arguments are omitted + +**Example — before:** +```rust +// Two separate macros — awkward +macro_rules! log { ($msg:expr) => { println!("{}", $msg) } } +macro_rules! log_with_level { ($level:expr, $msg:expr) => { ... } } +``` + +**Example — after:** +```rust +macro_rules! log { + ($msg:expr) => { log!(INFO, $msg) }; // default level + ($level:expr, $msg:expr) => { + println!("[{}] {}", stringify!($level), $msg); + }; + ($level:expr, $fmt:expr, $($arg:tt)*) => { // format args + println!("[{}] {}", stringify!($level), format!($fmt, $($arg)*)); + }; +} + +log!("simple"); // [INFO] simple +log!(WARN, "watch out"); // [WARN] watch out +log!(ERROR, "code {}", 42); // [ERROR] code 42 +``` + +--- + +## 5. #[derive] Custom Derive Macros + +### Source: + +1,116 `#[derive(...)]` usages. Common derives: Debug (869), +Clone (880), Copy (537), PartialEq (388), Eq (285). + +### Why + +Derive macros generate trait implementations automatically at +compile time. The compiler's built-in derives handle standard +traits. Third-party derives (serde, thiserror) can generate +complex code from annotations. + +### When to Use + +**Triggers:** +- Standard trait implementations (Debug, Clone, PartialEq, etc.) +- Serialization (serde::Serialize/Deserialize) +- Error generation (thiserror::Error) +- Any repetitive trait impl that follows a pattern + +**Example — before:** +```rust +struct Config { host: String, port: u16, debug: bool } + +impl fmt::Debug for Config { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Config") + .field("host", &self.host) + .field("port", &self.port) + .field("debug", &self.debug) + .finish() + } +} +impl Clone for Config { ... } +impl PartialEq for Config { ... } +// 50 lines of mechanical code +``` + +**Example — after:** +```rust +#[derive(Debug, Clone, PartialEq)] +struct Config { host: String, port: u16, debug: bool } +// One line. Same result. +``` + +--- + +## 6. Attribute Macros for Cross-Cutting Concerns + +### Source: + +```rust +#[test] +fn it_works() { ... } + +#[inline] +pub fn fast_path() { ... } + +#[cfg(target_os = "linux")] +fn linux_only() { ... } +``` + +28,639 `#[inline]` annotations in library/. + +### Why + +Attribute macros annotate items with metadata or transform them. +Built-in attributes (`test`, `inline`, `cfg`) direct the compiler. +Custom attribute macros (proc macros) can rewrite the annotated +item entirely. + +### When to Use + +**Triggers:** +- Performance hints (`#[inline]`, `#[cold]`) +- Test marking (`#[test]`, `#[bench]`) +- Conditional compilation (`#[cfg(...)]`) +- Framework integration (`#[tokio::main]`, `#[actix_web::get]`) + +### When NOT to Use + +**Don't use this when:** +- A regular function call would suffice +- The attribute hides too much magic (hard to debug) +- You're using `#[inline]` without benchmarking (let the compiler decide) + +--- + +## 7. macro_rules! for Type-Level Repetition + +### Source: + +The stdlib uses macros to implement traits for all numeric types: + +```rust +// core/src/num/mod.rs pattern (simplified) +macro_rules! int_impl { + ($Type:ty, $BITS:expr) => { + impl $Type { + pub const BITS: u32 = $BITS; + pub const MIN: Self = -(1 << ($BITS - 1)); + pub const MAX: Self = (1 << ($BITS - 1)) - 1; + + pub const fn wrapping_add(self, rhs: Self) -> Self { + // identical for all integer types + intrinsics::wrapping_add(self, rhs) + } + } + } +} + +int_impl!(i8, 8); +int_impl!(i16, 16); +int_impl!(i32, 32); +int_impl!(i64, 64); +int_impl!(i128, 128); +``` + +### Why + +When you need the SAME implementation for many types but generics +don't work (e.g., intrinsics that take specific types, or const +values that differ per type), macros generate the repetitive code. + +### When to Use + +**Triggers:** +- Same implementation for many concrete types +- Generics can't express the constraint (type-level constants) +- The difference between types is a value, not a behavior + +### When NOT to Use + +**Don't use this when:** +- Generics with trait bounds work (always prefer generics) +- The implementations actually differ in behavior (not just types) +- The macro output is so large it hurts compile time + +--- + +## 8. concat!, stringify!, file!, line! (Built-in Macros) + +### Source: + +```rust +// Built-in compiler macros used throughout: +panic!("assertion failed at {}:{}", file!(), line!()); +const NAME: &str = concat!("v", env!("CARGO_PKG_VERSION")); +``` + +### Why + +Built-in macros provide compile-time information that can't be +obtained any other way: file names, line numbers, environment +variables, string concatenation at compile time. + +### When to Use + +**Triggers:** +- Error messages with location info (`file!()`, `line!()`) +- Compile-time string building (`concat!()`) +- Compile-time assertions (`compile_error!()`) +- Environment info (`env!()`, `option_env!()`) + +--- + +## 9. Format Macros (println!, format!, write!) + +### Source: + +The format macro family is the most-used macro system in Rust: + +```rust +println!("Hello, {name}!"); // named argument +format!("{value:.2}"); // format specifier +write!(f, "{:#?}", self)?; // Debug with pretty-print +eprintln!("error: {err}"); // stderr +log::info!("request from {ip}"); // logging crate pattern +``` + +### Why + +Format macros are type-safe at compile time. `println!("{}", x)` +verifies that x implements Display. The compiler checks argument +count and types — no runtime format string bugs. + +### When to Use + +**Triggers:** +- String interpolation (building strings from values) +- Logging (use format macros, not string concatenation) +- Debug output (`{:?}` for Debug, `{:#?}` for pretty-print) + +### When NOT to Use + +**Don't use this when:** +- Building a string in a loop (`push_str` is more efficient + than repeated `format!`) +- The format string is determined at runtime (use `write!` with + explicit formatter) + +--- + +## 10. Macro Visibility and Export + +### Source: + +63 `#[macro_export]` macros in the stdlib. + +```rust +// Exported — available to users of the crate +#[macro_export] +macro_rules! vec { ... } + +// Not exported — internal to the crate +macro_rules! internal_helper { ... } +``` + +### Why + +By default, `macro_rules!` macros are scoped to the module they're +defined in. `#[macro_export]` puts them at the crate root (available +via `use my_crate::my_macro`). This is intentional — most macros +are implementation details. + +### When to Use + +**Triggers:** +- `#[macro_export]`: Users should be able to call this macro +- No export: Internal helper macro used within your crate only + +### When NOT to Use + +**Don't use this when:** +- A function would work (macros are harder to debug/document) +- The macro is only used in one module (keep it local) + +--- + +## Summary: Macro Decision Tree + +``` +Do you need a macro? +├── Can a function do it? → NO MACRO (use a function) +├── Can generics do it? → NO MACRO (use generics) +├── Need syntax transformation? → Macro +│ ├── Declarative (pattern matching)? → macro_rules! +│ │ ├── Internal only → no #[macro_export] +│ │ └── Public → #[macro_export] + $crate paths +│ └── Complex code generation? → Proc macro +│ ├── Derive impl → #[derive(MyTrait)] +│ ├── Attribute → #[my_attr] +│ └── Function-like → my_macro!(...) +└── Need compile-time info? → Built-in macros + +Writing macro_rules? +├── Accept trailing commas? → $(,)? (ALWAYS) +├── Multiple arg counts? → Multiple match arms +├── Used across crates? → $crate:: paths (hygiene) +└── Repetition over types? → $($Type:ty),* pattern +``` + +| Pattern | Use when | +|---|---| +| `macro_rules!` | Repetitive code that can't use generics | +| Trailing comma `$(,)?` | Every comma-separated macro (always) | +| `$crate` paths | Exported macros referencing own crate items | +| Multiple arms | Overloaded macro syntax | +| `#[derive]` | Automatic trait implementations | +| Attribute macros | Cross-cutting concerns (test, inline, cfg) | +| Type-level repetition | Same impl for many concrete types | +| Built-in macros | Compile-time info (file, line, env) | +| Format macros | Type-safe string interpolation | +| `#[macro_export]` | Making macros available to users | + +See also: +- [traits.md](traits.md) — When generics work instead of macros +- [api-design.md](api-design.md) — Public API design for macros +- [testing.md](testing.md) — Test assertion macros + +