docs: trait design patterns from rust-lang/rust

10 patterns, 699 lines. Full spec compliance.
Patterns: small traits, derive macros, From/Into, Default, Display,
Iterator, Deref, sealed traits, #[must_use], blanket implementations.
This commit is contained in:
Rodin
2026-04-30 14:55:01 -07:00
parent e58614de2e
commit b168acc900
+699
View File
@@ -0,0 +1,699 @@
# Rust Trait Patterns
Patterns for trait design in Rust, extracted from the standard
library source at rust-lang/rust.
**Source:** [rust-lang/rust](https://github.com/rust-lang/rust) at commit
[`f53b654`](https://github.com/rust-lang/rust/tree/f53b654a8882fd5fc036c4ca7a4ff41ce32497a6)
**Stats:** 241 public traits, 4,387 trait implementations, 508 From
impls, 895 Iterator impls, 269 Display impls, 226 Default impls.
---
## 1. Small Traits (One Required Method)
### Source:
[library/core/src/iter/traits/iterator.rs#L76](https://github.com/rust-lang/rust/blob/f53b654a8882fd5fc036c4ca7a4ff41ce32497a6/library/core/src/iter/traits/iterator.rs#L76)
```rust
// library/core/src/iter/traits/iterator.rs
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
// 70+ provided methods built on top of `next()`...
fn map<B, F>(self, f: F) -> Map<Self, F> { ... }
fn filter<P>(self, predicate: P) -> Filter<Self, P> { ... }
fn collect<B>(self) -> B where B: FromIterator<Self::Item> { ... }
// ...
}
```
Iterator has 1 required method (`next`) and 70+ provided methods.
Implementing Iterator for your type requires writing ONE function.
### Why
Minimize the cost of implementing the trait while maximizing the
value. One required method means trivial to implement; provided
methods give the full API for free. 895 Iterator implementations
exist in the stdlib because it's so easy.
### When to Use
**Triggers:**
- You want many types to implement your trait
- There's a clear "minimal kernel" that all implementations share
- Other methods can be defined in terms of that kernel
**Example — before:**
```rust
// Fat trait — every implementor must write 5 methods
trait Sequence {
fn next(&mut self) -> Option<Self::Item>;
fn size_hint(&self) -> (usize, Option<usize>);
fn count(self) -> usize;
fn last(self) -> Option<Self::Item>;
fn nth(&mut self, n: usize) -> Option<Self::Item>;
}
// Result: few types implement it because it's too much work
```
**Example — after:**
```rust
// Minimal required, rest provided
trait Sequence {
type Item;
fn next(&mut self) -> Option<Self::Item>; // ONE required method
// Provided defaults — free for all implementors:
fn size_hint(&self) -> (usize, Option<usize>) { (0, None) }
fn count(self) -> usize { self.fold(0, |c, _| c + 1) }
fn last(self) -> Option<Self::Item> { self.fold(None, |_, x| Some(x)) }
fn nth(&mut self, n: usize) -> Option<Self::Item> {
for _ in 0..n { self.next(); }
self.next()
}
}
```
### When NOT to Use
**Don't use this when:**
- Methods are genuinely independent (can't be derived from each other)
- Implementors SHOULD be forced to think about each method
(e.g., `PartialOrd` requires `partial_cmp` because you can't
derive comparison from equality)
- The "default" would be wrong for most types
---
## 2. Derive Macros for Standard Traits
### Source:
1,116 `#[derive(...)]` annotations in library/. Top derives:
Clone (880), Debug (869), Copy (537), PartialEq (388), Eq (285).
```rust
// Typical stdlib type:
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Duration {
secs: u64,
nanos: Nanoseconds,
}
```
### Why
Derive generates correct implementations mechanically. Hand-writing
Clone/Debug/PartialEq is error-prone and repetitive. The derive
order is a convention: Debug first, then Clone/Copy, then
comparison traits, then Hash.
### When to Use
**Triggers:**
- Your struct/enum fields all implement the trait you want to derive
- The derived behavior is correct (field-by-field comparison, etc.)
- You want standard trait behavior without custom logic
**Example — before:**
```rust
struct Point { x: f64, y: f64 }
impl fmt::Debug for Point {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Point")
.field("x", &self.x)
.field("y", &self.y)
.finish()
}
}
impl Clone for Point { ... }
impl PartialEq for Point { ... }
// 30 lines of boilerplate for standard behavior
```
**Example — after:**
```rust
#[derive(Debug, Clone, PartialEq)]
struct Point { x: f64, y: f64 }
// One line. Same result.
```
### When NOT to Use
**Don't use this when:**
- You need custom comparison logic (e.g., ignore certain fields)
- Your type contains types that don't implement the trait
- Derived behavior would be wrong (e.g., floating point and Eq)
### Anti-pattern
```rust
// DON'T: Derive Eq on floats
#[derive(PartialEq, Eq)] // COMPILE ERROR: f64 doesn't impl Eq
struct Measurement { value: f64 }
// DON'T: Derive PartialEq when it would be semantically wrong
#[derive(PartialEq)]
struct Handle { id: u64, cached_name: String }
// Do you really want cached_name to affect equality?
```
---
## 3. From/Into for Type Conversions
### Source:
[library/core/src/convert/mod.rs#L460](https://github.com/rust-lang/rust/blob/f53b654a8882fd5fc036c4ca7a4ff41ce32497a6/library/core/src/convert/mod.rs#L460)
508 `From<>` implementations in the stdlib.
```rust
// Blanket impl: implementing From gives you Into for free
impl<T, U> Into<U> for T where U: From<T> {
fn into(self) -> U { U::from(self) }
}
```
### Why
From/Into is the standard conversion mechanism. Always implement
`From` (not `Into`) because you get `Into` for free via the blanket
impl. The stdlib says: "This trait must not fail. If the conversion
can fail, use TryFrom."
### When to Use
**Triggers:**
- Conversion is infallible and lossless
- Converting between wrapper types (newtype → inner, inner → newtype)
- Error type conversion (see error handling patterns)
**Example — before:**
```rust
// Ad-hoc conversion methods with inconsistent names
impl Celsius {
fn to_fahrenheit(&self) -> Fahrenheit { ... }
}
impl Fahrenheit {
fn from_celsius(c: &Celsius) -> Fahrenheit { ... }
}
// Users must remember: is it .to_X() or ::from_X()?
```
**Example — after:**
```rust
impl From<Celsius> for Fahrenheit {
fn from(c: Celsius) -> Self {
Fahrenheit(c.0 * 9.0 / 5.0 + 32.0)
}
}
// Now works with: Fahrenheit::from(c), c.into(), and generic bounds
```
### When NOT to Use
**Don't use this when:**
- Conversion can fail (use `TryFrom`)
- Conversion is lossy (u32 → u16 can truncate)
- It's an expensive operation that shouldn't happen implicitly
---
## 4. Default for Sensible Zero Values
### Source:
[library/core/src/default.rs](https://github.com/rust-lang/rust/blob/f53b654a8882fd5fc036c4ca7a4ff41ce32497a6/library/core/src/default.rs)
226 `Default` implementations in the stdlib.
```rust
pub trait Default: Sized {
fn default() -> Self;
}
```
### Why
Provides a canonical "empty" or "zero" value. Used by `..Default::default()`
struct update syntax, `Option::unwrap_or_default()`, collection
initialization, and generic code that needs to create values.
### When to Use
**Triggers:**
- Your type has an obvious "empty" state (empty string, zero, nil)
- You want `..Default::default()` to work for partial struct init
- Your type is used in generics bounded by `Default`
**Example — before:**
```rust
let config = Config {
timeout: Duration::from_secs(30),
retries: 3,
verbose: false,
port: 8080,
host: String::new(),
};
// Must specify EVERY field even for "default" values
```
**Example — after:**
```rust
#[derive(Default)]
struct Config {
timeout: Duration, // Default: 0s (may need custom impl)
retries: u32, // Default: 0
verbose: bool, // Default: false
port: u16, // Default: 0
host: String, // Default: ""
}
let config = Config {
port: 8080,
retries: 3,
..Default::default() // everything else gets defaults
};
```
### When NOT to Use
**Don't use this when:**
- There's no meaningful default (what's a "default" database URL?)
- The zero value would be dangerous (default timeout of 0 = infinite)
- You WANT users to think about every field
---
## 5. Display for Human-Readable Output
### Source:
[library/core/src/fmt/mod.rs#L1187](https://github.com/rust-lang/rust/blob/f53b654a8882fd5fc036c4ca7a4ff41ce32497a6/library/core/src/fmt/mod.rs#L1187)
269 Display implementations in the stdlib.
```rust
pub trait Display: PointeeSized {
fn fmt(&self, f: &mut Formatter<'_>) -> Result;
}
```
### Why
Display is for user-facing output (what `println!("{}", x)` uses).
Debug is for developer-facing output (`println!("{:?}", x)`). Every
public type should implement both. Error trait REQUIRES Display.
### When to Use
**Triggers:**
- Your type will be printed/logged for end users
- Your type implements Error (Display is required)
- You want `format!("{}", value)` to work
- The output should be human-readable (not debug-formatted)
**Example — before:**
```rust
// Only Debug — ugly output for users
#[derive(Debug)]
struct Version { major: u32, minor: u32, patch: u32 }
println!("{:?}", v); // "Version { major: 1, minor: 2, patch: 3 }" — ugly
```
**Example — after:**
```rust
#[derive(Debug)]
struct Version { major: u32, minor: u32, patch: u32 }
impl fmt::Display for Version {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
}
}
println!("{}", v); // "1.2.3" — clean
```
### When NOT to Use
**Don't use this when:**
- There's no single canonical human representation
- The type is internal and never shown to users
- Debug output is sufficient (use `#[derive(Debug)]`)
---
## 6. Iterator Implementation
### Source:
[library/core/src/iter/traits/iterator.rs](https://github.com/rust-lang/rust/blob/f53b654a8882fd5fc036c4ca7a4ff41ce32497a6/library/core/src/iter/traits/iterator.rs)
895 Iterator implementations in the stdlib.
### Why
Implementing Iterator gives you the entire iterator ecosystem for
free: `.map()`, `.filter()`, `.collect()`, `for` loops, and
composability with every other iterator adapter.
### When to Use
**Triggers:**
- Your type represents a sequence of values
- Users should be able to loop over it with `for`
- You want composability with the iterator ecosystem
**Example — before:**
```rust
struct Fibonacci { a: u64, b: u64 }
impl Fibonacci {
fn next_value(&mut self) -> u64 {
let result = self.a;
let new_b = self.a + self.b;
self.a = self.b;
self.b = new_b;
result
}
}
// Can't use with for loops, map, filter, take, collect...
```
**Example — after:**
```rust
struct Fibonacci { a: u64, b: u64 }
impl Iterator for Fibonacci {
type Item = u64;
fn next(&mut self) -> Option<Self::Item> {
let result = self.a;
let new_b = self.a.checked_add(self.b)?; // overflow → end
self.a = self.b;
self.b = new_b;
Some(result)
}
}
// Now works with everything:
let first_10: Vec<u64> = Fibonacci { a: 0, b: 1 }.take(10).collect();
```
### When NOT to Use
**Don't use this when:**
- Your type doesn't represent a sequence
- Access is random (implement Index instead)
- The "iteration" has side effects that shouldn't compose
---
## 7. Deref for Smart Pointer Transparency
### Source:
[library/core/src/ops/deref.rs#L60](https://github.com/rust-lang/rust/blob/f53b654a8882fd5fc036c4ca7a4ff41ce32497a6/library/core/src/ops/deref.rs#L60)
```rust
pub trait Deref {
type Target: ?Sized;
fn deref(&self) -> &Self::Target;
}
```
### Why
Deref coercion makes smart pointers transparent: `Box<T>` behaves
like `T`, `String` behaves like `str`, `Vec<T>` behaves like `[T]`.
The compiler inserts `.deref()` calls implicitly.
### When to Use
**Triggers:**
- Your type is a smart pointer or wrapper around a single inner value
- You want method calls to "pass through" to the inner type
- There's a clear "target" type that your wrapper contains
**Example — before:**
```rust
struct MyString(Vec<u8>);
impl MyString {
fn as_str(&self) -> &str {
std::str::from_utf8(&self.0).unwrap()
}
fn len(&self) -> usize { self.as_str().len() }
fn is_empty(&self) -> bool { self.as_str().is_empty() }
fn contains(&self, pat: &str) -> bool { self.as_str().contains(pat) }
// Must manually delegate EVERY str method...
}
```
**Example — after:**
```rust
struct MyString(Vec<u8>);
impl Deref for MyString {
type Target = str;
fn deref(&self) -> &str {
std::str::from_utf8(&self.0).unwrap()
}
}
// Now ALL str methods work automatically:
// my_string.len(), my_string.contains("x"), my_string.to_uppercase()
```
### When NOT to Use
**Don't use this when:**
- Your type is not semantically a wrapper/pointer
- The deref target is surprising to users
- You're using Deref as inheritance (Rust doesn't have inheritance —
this abuse leads to confusing APIs)
### Anti-pattern
```rust
// DON'T: Use Deref as "inheritance"
struct Dog { name: String }
struct GuideDog { dog: Dog, handler: String }
impl Deref for GuideDog {
type Target = Dog;
fn deref(&self) -> &Dog { &self.dog }
}
// This makes GuideDog "look like" Dog — misleading.
// Use composition + explicit delegation instead.
```
---
## 8. Sealed Traits (Restrict Implementors)
### Source:
[library/portable-simd/crates/core_simd/src/cast.rs](https://github.com/rust-lang/rust/blob/f53b654a8882fd5fc036c4ca7a4ff41ce32497a6/library/portable-simd/crates/core_simd/src/cast.rs)
```rust
mod sealed {
pub trait Sealed {}
}
pub trait SimdCast: Sealed + SimdElement {}
```
### Why
A sealed trait can only be implemented within its defining crate.
External crates can USE the trait but not IMPLEMENT it. This gives
the library author freedom to add methods without breaking changes.
### When to Use
**Triggers:**
- You want to add trait methods in future versions without breaking
downstream code
- The set of valid implementors is fixed and known
- You're publishing a library and need API stability guarantees
**Example — before:**
```rust
// Public trait — anyone can implement it
pub trait Codec {
fn encode(&self, data: &[u8]) -> Vec<u8>;
// Can NEVER add decode() without breaking all implementors
}
```
**Example — after:**
```rust
mod private { pub trait Sealed {} }
pub trait Codec: private::Sealed {
fn encode(&self, data: &[u8]) -> Vec<u8>;
fn decode(&self, data: &[u8]) -> Vec<u8>; // safe to add later!
}
// Only YOUR crate can implement Codec because only YOUR crate
// can implement private::Sealed
impl private::Sealed for JsonCodec {}
impl Codec for JsonCodec { ... }
```
### When NOT to Use
**Don't use this when:**
- Users should be able to add their own implementations
- You're writing application code (not a library)
- The trait is intentionally an extension point
---
## 9. #[must_use] on Types and Functions
### Source:
1,963 `#[must_use]` annotations across the stdlib.
```rust
#[must_use = "this `Result` may be an `Err` variant, which should be handled"]
pub enum Result<T, E> { ... }
#[must_use = "iterators are lazy and do nothing unless consumed"]
pub struct Map<I, F> { ... }
```
### Why
The compiler warns when a `#[must_use]` value is ignored. This
catches bugs where you call a function for its return value but
forget to use it. Iterator adapters are all `#[must_use]` because
they're lazy — creating a `.map()` does nothing until collected.
### When to Use
**Triggers:**
- Ignoring the return value is always a bug
- The function/type is lazy (nothing happens without consumption)
- Builder methods that return a new value (not &mut self)
**Example — before:**
```rust
fn sorted(mut v: Vec<i32>) -> Vec<i32> {
v.sort();
v
}
let data = vec![3, 1, 2];
sorted(data); // BUG: result discarded, data is gone, nothing happened
```
**Example — after:**
```rust
#[must_use = "sorted returns the sorted vec; the original is consumed"]
fn sorted(mut v: Vec<i32>) -> Vec<i32> {
v.sort();
v
}
sorted(data); // COMPILER WARNING: unused return value of `sorted`
```
### When NOT to Use
**Don't use this when:**
- The function is called for side effects (e.g., `println!`)
- The return value is informational but not critical
---
## 10. Blanket Implementations
### Source:
[library/core/src/convert/mod.rs](https://github.com/rust-lang/rust/blob/f53b654a8882fd5fc036c4ca7a4ff41ce32497a6/library/core/src/convert/mod.rs) (From → Into blanket)
```rust
// Anyone who implements From gets Into for free:
impl<T, U> Into<U> for T where U: From<T> {
fn into(self) -> U { U::from(self) }
}
// Every type can be converted from itself:
impl<T> From<T> for T {
fn from(t: T) -> T { t }
}
```
### Why
Blanket impls provide functionality to ALL types that satisfy a
bound, without requiring each type to opt in. This is how Rust
scales — you implement one trait and get many others for free.
### When to Use
**Triggers:**
- Trait B can be derived from trait A for ALL types
- You want to reduce the number of impls users must write
- There's a clear mathematical relationship (From ↔ Into)
### When NOT to Use
**Don't use this when:**
- The blanket would conflict with specific implementations
- Not all types satisfying the bound actually want the blanket
- The blanket would surprise users or violate expectations
---
## Summary: Trait Design Decision Tree
```
Designing a new trait?
├── How many required methods?
│ ├── Can provide defaults built on a kernel → 1 required + many provided
│ └── Methods are independent → require each one
├── Who should implement it?
│ ├── Everyone → keep it simple, consider derive macro
│ ├── Only your crate → seal it (mod private { Sealed })
│ └── External + internal → leave it open, use #[non_exhaustive] on enums
├── Standard traits to implement on your types?
│ ├── Always: Debug
│ ├── If meaningful: Clone, PartialEq, Eq, Hash, Default
│ ├── If printable: Display
│ ├── If sequential: Iterator
│ ├── If wrapper: Deref
│ └── If convertible: From (not Into)
└── Should result be ignored?
└── Never → add #[must_use]
```
| When you need to... | Use |
|---|---|
| Provide rich API from minimal impl | One required method + provided defaults |
| Standard trait behavior | `#[derive(Debug, Clone, PartialEq, ...)]` |
| Type conversion | `impl From<Source> for Target` |
| Sensible zero/empty value | `impl Default` (or derive) |
| Human-readable output | `impl Display` |
| Iteration/composition | `impl Iterator` |
| Smart pointer transparency | `impl Deref` |
| Restrict implementors to your crate | Sealed trait pattern |
| Prevent value discard | `#[must_use]` |
| Free impls for all qualifying types | Blanket implementation |
See also:
- [error-handling.md](error-handling.md) — Error trait + From impls
- [ownership.md](ownership.md) — Clone, Copy, Drop trait usage
- [api-design.md](api-design.md) — Public trait design guidelines
<!-- PATTERN_COMPLETE -->