Deriving and Common Trait Implementations
Rule
Derive standard traits when their semantics are obvious, hand-write Display, and avoid semantics-heavy derives by habit.
Why
Derived impls are correct when structural behavior matches domain behavior; otherwise they can expose secrets or promise misleading equality, ordering, defaults, or clone semantics.
Do
- Derive
Debugfor ordinary data and redact secret-bearing types manually. - Derive
Clonefor value semantics with acceptable cost. - Derive
Copyonly for small scalar-like values with unsurprising duplication. - Derive equality and hashing only when the same fields define both.
- Derive ordering only when one total order is obvious.
- Implement
Defaultonly for a valid, useful, unsurprising value. - Hand-write
Displayfor stable user-facing text. - Keep manual equality, hashing, and ordering implementations coherent.
Avoid
- Do not derive traits solely to satisfy a temporary test or log statement.
- Do not derive
Debugfor credentials or tokens. - Do not derive
Copyfor resources or types likely to gain owned data. - Do not derive arbitrary ordering or invalid defaults.
- Do not use
Displayfor programmer-only diagnostics. - Do not derive serialization unless a wire format is intentional.
Public API Notes
Public trait impls are compatibility surface. Add only semantics the type can support over time.
Example
#![allow(unused)]
fn main() {
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub struct UserId(u64);
#[derive(Clone, Eq, PartialEq)]
pub struct ApiToken(String);
impl std::fmt::Debug for ApiToken {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter.write_str("ApiToken(<redacted>)")
}
}
}
Exceptions
- Keep impl surface smaller while a public type’s semantics are unsettled.
- Add derives freely to test-only helpers when they do not leak into production API.
- Hand-write traits when domain behavior differs from field-by-field behavior.