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.
This commit is contained in:
@@ -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
|
||||
|
||||
<!-- PATTERN_COMPLETE -->
|
||||
Reference in New Issue
Block a user