TL;DR:
#[derive(TraitName)]is an attribute that tells the compiler to automatically generate a trait implementation for your struct or enum. It saves you from writing repetitive boilerplate. The compiler generates the implementation by applying the trait to each field recursively. Built-in derivable traits includeDebug,Clone,Copy,PartialEq,Eq,Hash,Default, and more. Crates likeserdeadd their own:Serialize,Deserialize.
How Does #[derive] Work?
#[derive] is a procedural macro that runs at compile time, inspecting your type's structure and generating a trait implementation for each field or variant.
#[derive(Debug, Clone, PartialEq)]
struct Point {
x: f64,
y: f64,
}
fn main() {
let p1 = Point { x: 1.0, y: 2.0 };
let p2 = p1.clone(); // Clone generated
println!("{:?}", p1); // Debug generated
println!("{}", p1 == p2); // PartialEq generated; true
}The generated Debug impl for Point produces Point { x: 1.0, y: 2.0 }. The generated Clone calls .clone() on each field. The generated PartialEq compares each field with ==.
Which Traits Can Be Derived?
The standard library provides 14 derivable traits. Each requires that all fields also implement the same trait.
| Trait | What it generates | Requires |
|---|---|---|
Debug | {:?} formatting | All fields: Debug |
Clone | .clone() | All fields: Clone |
Copy | Implicit copy on assign | All fields: Copy + Clone |
PartialEq | == and != | All fields: PartialEq |
Eq | Full equality (marker) | PartialEq |
PartialOrd | <, > (partial) | All fields: PartialOrd |
Ord | .cmp(), sorting | Eq + all fields: Ord |
Hash | Use in HashMap keys | All fields: Hash |
Default | Type::default() | All fields: Default |
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct UserId(u64); // newtype; derives work on tuple structs too
#[derive(Debug, Default)]
struct Config {
timeout_ms: u64, // default: 0
retries: u32, // default: 0
verbose: bool, // default: false
}
let cfg = Config::default();
println!("{cfg:?}"); // Config { timeout_ms: 0, retries: 0, verbose: false }What Does serde's #[derive] Add?
serde::Serialize and serde::Deserialize are the most commonly used third-party derive macros; they generate JSON/TOML/YAML serialization code.
[dependencies]
serde = { version = "1", features = ["derive"] }
serde_json = "1"use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
struct User {
id: u64,
name: String,
#[serde(skip_serializing_if = "Option::is_none")]
email: Option<String>,
}
fn main() {
let user = User { id: 1, name: "Alice".into(), email: None };
let json = serde_json::to_string(&user).unwrap();
println!("{json}"); // {"id":1,"name":"Alice"}
let back: User = serde_json::from_str(&json).unwrap();
println!("{back:?}");
}When Should You Implement Manually Instead of Deriving?
Derive when the default field-by-field implementation is correct. Implement manually when you need custom logic; ordering by a specific field, redacting secrets from Debug output, or partial equality.
use std::fmt;
// Manual Debug; hide the password field
struct Credentials {
username: String,
password: String,
}
impl fmt::Debug for Credentials {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Credentials")
.field("username", &self.username)
.field("password", &"[REDACTED]")
.finish()
}
}
// Manual PartialEq; compare only by ID, not all fields
#[derive(Debug)]
struct Record {
id: u64,
data: Vec<u8>, // large, don't compare
}
impl PartialEq for Record {
fn eq(&self, other: &Self) -> bool {
self.id == other.id
}
}How Do You Write a Custom Derive Macro?
Custom derive macros are procedural macros that parse your type's structure at compile time using syn and generate code using quote.
# In a proc-macro crate's Cargo.toml
[lib]
proc-macro = true
[dependencies]
syn = { version = "2", features = ["full"] }
quote = "1"
proc-macro2 = "1"Writing custom derives is advanced; most Rust developers use existing derives from the ecosystem (serde, thiserror, clap, sqlx) rather than writing their own.
Frequently Asked Questions
Copy is a subtrait of Clone; every Copy type must also be Clone. This makes the type system consistent: if you can implicitly copy a value, you can certainly explicitly clone it.
No; derived implementations require all fields to implement the same trait. If one field doesn't implement Debug, you cannot #[derive(Debug)]. Either implement manually or wrap the field in a type that does implement the trait.
PartialEq allows a != a (like f32::NAN). Eq asserts full reflexive equality (a == a is always true). Eq is a marker trait (no methods) and requires PartialEq. For most types, derive both together.
Procedural macros (like serde's derive) can slow compilation; each derive runs Rust code at compile time. For large codebases, this is usually acceptable. If compile times are critical, consider reducing derive usage or using typetag alternatives.
Sources
Related Glossary Terms
- Trait:
#[derive]generates trait implementations automatically - Macro:
#[derive]is a procedural macro that generates code at compile time - Serde:
#[derive(Serialize, Deserialize)]is serde's derive API - Struct: Derives are most commonly used on structs and enums
- Enum:
#[derive(Debug, PartialEq)]works on enums too - Clap: clap's derive API turns structs and enums into CLI parsers
- Copy/Clone:
CopyandCloneare among the most common traits developers derive first - proc-macro: Every custom derive in Rust is implemented as a procedural macro under the hood
- SeaORM: SeaORM relies heavily on derive-based entity definitions and model generation
Keep Reading
- Rust Serde Guide: derive(Serialize, Deserialize) is the most-used derive macro in Rust
- Rust Generics and Traits Explained: derive generates trait implementations automatically
- Learn Rust in 2026: derive macros appear in almost every Rust project
