# 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