Deriving Traits
#[derive(...)] asks the compiler to auto-implement standard traits for structs and enums with correct field-wise logic.
Recipe
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct UserId(u64);
#[derive(Debug, serde::Serialize, serde::Deserialize)]
struct User {
id: UserId,
name: String,
}When to reach for this: Rapid impl of Debug, Clone, equality, ordering, and serde when field semantics match derived behavior.
Working Example
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
enum Priority { Low, Medium, High }
fn main() {
use std::collections::BTreeSet;
let mut set = BTreeSet::new();
set.insert(Priority::High);
set.insert(Priority::Low);
println!("{:?}", set);
}What this demonstrates:
Ordderive on enums requires discriminant orderingHash+EqenableHashSet/HashMapkeysCopyadded when all fieldsCopy- Custom traits still need manual
impl
Deep Dive
Common Derives
| Trait | Purpose |
|---|---|
Debug | {:?} formatting |
Clone | .clone() duplication |
Copy | Implicit copy (stack bitwise) |
PartialEq/Eq | == equality |
PartialOrd/Ord | sorting comparisons |
Hash | hash maps/sets |
Default | Default::default() |
#[derive(Default)]
Picks first variant for enums or requires #[default] attribute on chosen variant (Rust 1.62+).
Cannot Derive
Display, Drop, From for arbitrary logic - manual impl or separate crates (thiserror, strum).
Gotchas
- Derive
Eqon floats - Impossible. Fix: Use integers orpartial_cmpmanually. - Derive
Ordon floats - Not total order. Fix: Manual impl withpartial_cmpwrapper type. - Large
Clonecost hidden - Derive clones all fields. Fix: UseArcinside or avoidClone. - Derived
Hashchanges with field order - Reordering fields changes hash (same type). Fix: Stable schema versioning for persisted hashes. - Conflicting manual + derive - Cannot both. Fix: Remove derive for that trait.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
Manual impl | Custom equality/display | Standard structural behavior |
derive_more crate | Boilerplate newtypes | Std derive suffices |
thiserror/strum | Errors/enums strings | Simple structs |
serde derive | JSON/config | No serialization needed |
FAQs
Where does derive expand?
Compiler generates `impl` at compile time - use `cargo expand` to inspect.Derive on generics?
`#[derive(Debug)] struct Wrapper<T>(T)` requires `T: Debug`.Helper attributes?
`#[serde(skip)]`, `#[default]`, `#[derivative(...)]` with extra crates.Can enums derive `Hash`?
Yes if all variant payloads hashable.`Copy` + `Clone` together?
Common - `Copy` requires `Clone`.Derive `Debug` for secrets?
Leaks in logs - custom `Debug` redacting fields.Union derive?
Derives limited on unions - mostly manual unsafe.Transparent newtype?
`#[repr(transparent)]` + derive preserves layout for FFI.Multiple derives order?
Order among traits in attribute does not matter.Performance?
Derived methods inline; same as hand-written structural impls.Related
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.