Introduction
Manually implementing common traits like Debug, Clone, and PartialEq for every struct would be tedious and error-prone. Rust's #[derive] attribute macro generates these implementations automatically, and it is one of the most frequently used features in the language.
Key Concepts
- #[derive(...)]: An attribute macro placed above a struct or enum definition. It tells the compiler to auto-generate trait implementations based on the struct's fields.
- Derivable traits: Traits whose implementations can be mechanically generated. The standard library provides about a dozen, including
Debug,Clone,Copy,PartialEq,Eq,Hash,Default,PartialOrd, andOrd. - Copy semantics: Types that implement
Copyare duplicated implicitly on assignment instead of being moved.CopyrequiresCloneand only works for types whose fields are allCopy(no heap data).
Real World Context
Almost every struct in a real Rust codebase has at least #[derive(Debug)]. Data transfer objects typically derive Debug, Clone, PartialEq. Types used as HashMap keys need Eq + Hash. The serde library extends derive with Serialize and Deserialize, making JSON/YAML parsing a one-liner.
Deep Dive
You apply derive by listing the traits in the attribute:
rust#[derive(Debug, Clone, PartialEq)] struct Point { x: f64, y: f64, }
Now Point can be printed with {:?}, cloned with .clone(), and compared with ==.
The Copy trait enables implicit copies instead of moves. It requires Clone and only works when all fields are Copy:
rust#[derive(Debug, Clone, Copy)] struct Pixel { r: u8, g: u8, b: u8 } let p1 = Pixel { r: 255, g: 0, b: 0 }; let p2 = p1; // Copy, not move! println!("{:?} {:?}", p1, p2); // Both valid
A struct with String fields cannot derive Copy because String is heap-allocated.
The Default trait provides a ::default() constructor, combinable with struct update syntax:
rust#[derive(Default, Debug)] struct Config { debug: bool, port: u16, name: String, } let config = Config { debug: true, ..Default::default() };
For HashMap keys, you need both Eq and Hash:
rust#[derive(Debug, Clone, PartialEq, Eq, Hash)] struct UserId(u64);
Common Pitfalls
- Deriving
Copyon types with heap data — If any field isString,Vec, or another non-Copy type, the derive will fail with a compile error. UseClonealone for these types. - Forgetting
Eqfor HashMap keys —PartialEqalone is not enough. HashMap requiresEq(which guarantees reflexivity:a == ais always true). Floats implementPartialEqbut notEq, so they cannot be HashMap keys.
Best Practices
- Always derive
Debug— It costs nothing at runtime and makes debugging dramatically easier. There is almost never a reason to omit it. - Derive the minimum set you need — Do not blindly derive every trait. Each derived trait adds to compile time and creates API commitments. Derive
Clonewhen cloning is needed,Copyonly for small, stack-only types.
Summary
#[derive(...)]auto-generates trait implementations from struct/enum fields.Debugshould be on virtually every type;CloneandPartialEqare also common.Copyrequires all fields to beCopyand enables implicit duplication.Eq + Hashare required for HashMap keys.Defaultprovides zero-value constructors.
Code Examples
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct UserId(u64);
use std::collections::HashMap;
let mut users = HashMap::new();
users.insert(UserId(1), "Alice");
users.insert(UserId(2), "Bob");
// Works because UserId implements Hash + Eq
if let Some(name) = users.get(&UserId(1)) {
println!("Found: {name}");
}