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:
@@ -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 -->
|
||||
Reference in New Issue
Block a user