Derive Macros in Rust
The #[derive(...)] attribute tells the Rust compiler to automatically generate
a trait implementation for your type. Instead of writing dozens of lines of
boilerplate, you list the traits you want and Rust writes the code for you — at
compile time, with zero runtime overhead.
This page covers all standard derivable traits and when to use them.
How Derive Works
#[derive(...)] is a proc-macro (procedural macro) that runs at compile time.
It reads the definition of your type, generates the appropriate impl block, and
inserts it into the compiled output. The generated code is exactly what you would
write by hand — derive just automates the mechanical parts.
One key requirement: every field (or variant) in your type must itself implement the derived trait, or the compiler will refuse.
// Without derive — manual boilerplate
struct Point { x: i32, y: i32 }
impl std::fmt::Debug for Point {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Point")
.field("x", &self.x)
.field("y", &self.y)
.finish()
}
}
// With derive — equivalent output, zero boilerplate
#[derive(Debug)]
struct PointDerived { x: i32, y: i32 }
fn main() {
let p = PointDerived { x: 3, y: 7 };
println!("{:?}", p); // PointDerived { x: 3, y: 7 }
println!("{:#?}", p); // pretty-printed
}PointDerived { x: 3, y: 7 }
PointDerived {
x: 3,
y: 7,
}Debug
Debug enables {:?} and {:#?} formatting. It is used everywhere — in
println!, dbg!, test failure output, and error messages. Nearly every type
you create should derive Debug.
#[derive(Debug)]
struct User {
id: u32,
name: String,
admin: bool,
}
#[derive(Debug)]
enum Role { Guest, Editor, Admin }
fn main() {
let user = User { id: 1, name: String::from("Alice"), admin: true };
println!("{:?}", user); // compact
println!("{:#?}", user); // pretty-printed
// dbg! prints file/line and the value, then returns it
let role = dbg!(Role::Admin);
println!("{:?}", role);
}User { id: 1, name: "Alice", admin: true }
User {
id: 1,
name: "Alice",
admin: true,
}
[src/main.rs:14] Role::Admin = Admin
AdminClone and Copy
Clone provides a .clone() method that creates a deep copy of the value.
Copy marks types that can be duplicated by simply copying their bytes — no
heap allocation, no custom logic. Copy requires Clone and can only be derived
if all fields are also Copy.
#[derive(Debug, Clone)] // Clone only — String is not Copy
struct Config {
host: String,
port: u16,
}
#[derive(Debug, Clone, Copy)] // Copy + Clone — all fields are Copy
struct Point { x: f32, y: f32 }
fn main() {
// Clone: explicit deep copy
let cfg1 = Config { host: String::from("localhost"), port: 8080 };
let cfg2 = cfg1.clone(); // cfg1 is still valid
println!("{:?}", cfg1);
println!("{:?}", cfg2);
// Copy: implicit bitwise copy — no .clone() needed
let p1 = Point { x: 1.0, y: 2.0 };
let p2 = p1; // copied, not moved
println!("{:?} {:?}", p1, p2); // p1 still usable
}PartialEq and Eq
PartialEq enables the == and != operators. Eq is a marker trait
that says equality is a full equivalence relation (reflexive, symmetric, transitive
— and every value equals itself). Derive both together unless your type contains a
float, which is not Eq because NaN != NaN.
#[derive(Debug, PartialEq, Eq)]
struct UserId(u64);
#[derive(Debug, PartialEq, Eq)]
enum Status { Active, Inactive, Banned }
#[derive(Debug, PartialEq)] // no Eq — f64 is not Eq (NaN != NaN)
struct Measurement { value: f64, unit: String }
fn main() {
let a = UserId(42);
let b = UserId(42);
let c = UserId(99);
println!("{}", a == b); // true
println!("{}", a == c); // false
println!("{}", a != c); // true
assert_eq!(Status::Active, Status::Active);
assert_ne!(Status::Active, Status::Banned);
// PartialEq works in assertions, HashMaps, Vec::contains, etc.
let ids = vec![UserId(1), UserId(2), UserId(3)];
println!("{}", ids.contains(&UserId(2))); // true
}PartialOrd and Ord
PartialOrd enables <, >, <=, >= comparisons. Ord is the total
ordering version — it adds .min(), .max(), .clamp(), and sorting. Both
require PartialEq; Ord also requires Eq.
Derived ordering compares fields lexicographically in the order they are declared.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
struct Version {
major: u32,
minor: u32,
patch: u32,
}
fn main() {
let v1 = Version { major: 1, minor: 2, patch: 3 };
let v2 = Version { major: 1, minor: 3, patch: 0 };
let v3 = Version { major: 2, minor: 0, patch: 0 };
println!("{}", v1 < v2); // true (minor 2 < 3)
println!("{}", v2 < v3); // true (major 1 < 2)
let mut versions = vec![v3, v1, v2];
versions.sort();
println!("{:?}", versions);
// [Version { major: 1, minor: 2, patch: 3 },
// Version { major: 1, minor: 3, patch: 0 },
// Version { major: 2, minor: 0, patch: 0 }]
}Hash
Hash allows a type to be used as a key in HashMap and HashSet. It must
be consistent with PartialEq: if a == b then hash(a) == hash(b). Always
derive Hash together with PartialEq and Eq to maintain this invariant.
use std::collections::{HashMap, HashSet};
#[derive(Debug, PartialEq, Eq, Hash)]
struct Language {
name: String,
version: u32,
}
fn main() {
// Use as a HashMap key
let mut benchmarks: HashMap<Language, f64> = HashMap::new();
benchmarks.insert(Language { name: String::from("Rust"), version: 2021 }, 1.0);
benchmarks.insert(Language { name: String::from("C"), version: 17 }, 1.05);
benchmarks.insert(Language { name: String::from("Python"), version: 3 }, 12.3);
for (lang, factor) in &benchmarks {
println!("{} v{}: {:.1}x slower than baseline", lang.name, lang.version, factor);
}
// Use as a HashSet element
let popular: HashSet<Language> = HashSet::from([
Language { name: String::from("Rust"), version: 2021 },
Language { name: String::from("TypeScript"), version: 5 },
]);
println!("Contains Rust: {}", popular.contains(&Language {
name: String::from("Rust"),
version: 2021,
}));
}Default
Default provides a Default::default() constructor that returns a sensible
zero value. When derived, each field's Default::default() is called. Common
defaults: 0 for numbers, false for bool, "" for String, None for Option.
#[derive(Debug, Default)]
struct AppConfig {
host: String, // ""
port: u16, // 0
workers: usize, // 0
debug: bool, // false
timeout: Option<u64>, // None
}
fn main() {
// Full default
let cfg = AppConfig::default();
println!("{:#?}", cfg);
// Struct update syntax — override only what you need
let production = AppConfig {
host: String::from("0.0.0.0"),
port: 443,
workers: 8,
..Default::default() // debug=false, timeout=None
};
println!("{} on port {} with {} workers", production.host, production.port, production.workers);
}Deriving Multiple Traits at Once
You can list multiple traits in a single #[derive(...)] attribute. Order
within the list does not matter, but there is a conventional ordering that most
Rust projects follow.
// Conventional ordering: Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
struct Priority(u8);
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct Task {
id: u64,
title: String,
priority: Priority,
}
fn main() {
let p1 = Priority(1);
let p2 = Priority(5);
println!("{:?} < {:?}: {}", p1, p2, p1 < p2); // true
let mut tasks = vec![
Task { id: 1, title: String::from("Write tests"), priority: Priority(2) },
Task { id: 2, title: String::from("Fix bug"), priority: Priority(5) },
Task { id: 3, title: String::from("Update docs"), priority: Priority(1) },
];
tasks.sort_by_key(|t| t.priority);
for t in &tasks {
println!("[P{}] {}", t.priority.0, t.title);
}
}Priority(1) < Priority(5): true [P1] Update docs [P2] Write tests [P5] Fix bug
Debug on Enums
#[derive(Debug)] on enums shows the variant name and any associated data.
This is invaluable for debugging and logging.
#[derive(Debug, PartialEq)]
enum Event {
Click { x: i32, y: i32 },
KeyPress(char),
Resize { width: u32, height: u32 },
Quit,
}
fn main() {
let events = vec![
Event::Click { x: 100, y: 200 },
Event::KeyPress('q'),
Event::Resize { width: 1920, height: 1080 },
Event::Quit,
];
for e in &events {
println!("{:?}", e);
}
// Click { x: 100, y: 200 }
// KeyPress('q')
// Resize { width: 1920, height: 1080 }
// Quit
assert_eq!(events[3], Event::Quit);
}The serde Derives
The serde crate provides two widely-used derive macros: Serialize and
Deserialize. These enable converting your types to and from JSON, TOML, YAML,
MessagePack, and dozens of other formats.
Unlike the standard derives, serde macros come from an external crate and require
adding serde to your Cargo.toml.
// Cargo.toml:
// [dependencies]
// serde = { version = "1", features = ["derive"] }
// serde_json = "1"
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
struct User {
id: u32,
name: String,
email: String,
#[serde(default)] // use Default::default() if missing in input
admin: bool,
#[serde(skip)] // never serialized or deserialized
password_hash: String,
}
fn main() {
let user = User {
id: 1,
name: String::from("Alice"),
email: String::from("alice@example.com"),
admin: false,
password_hash: String::from("secret"),
};
// Serialize to JSON
let json = serde_json::to_string_pretty(&user).unwrap();
println!("{}", json);
// Deserialize back
let parsed: User = serde_json::from_str(&json).unwrap();
println!("Parsed: {} <{}>", parsed.name, parsed.email);
}When to Implement Manually Instead of Deriving
Derive generates a structural implementation — it compares or hashes each field in declaration order. Sometimes you need different semantics and must implement manually.
// Case-insensitive string wrapper
#[derive(Debug, Clone)]
struct CiString(String);
// Derived PartialEq would be case-sensitive — we need case-insensitive
impl PartialEq for CiString {
fn eq(&self, other: &Self) -> bool {
self.0.to_lowercase() == other.0.to_lowercase()
}
}
impl Eq for CiString {}
// Hash must be consistent with PartialEq:
// if a == b, then hash(a) must equal hash(b)
impl std::hash::Hash for CiString {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.to_lowercase().hash(state);
}
}
fn main() {
let a = CiString(String::from("Rust"));
let b = CiString(String::from("rust"));
println!("{}", a == b); // true — case-insensitive
use std::collections::HashSet;
let mut set = HashSet::new();
set.insert(a);
println!("{}", set.contains(&b)); // true — same hash bucket
}Scenario | Use derive? |
|---|---|
Structural comparison (all fields equal) | Yes — derive PartialEq, Eq |
Custom equality (e.g. case-insensitive) | No — implement manually |
Lexicographic ordering by all fields | Yes — derive Ord |
Custom ordering (e.g. by one field only) | No — implement manually or use sort_by_key |
Standard Debug format | Yes — derive Debug |
Custom Display / Debug format | No — implement fmt::Display / fmt::Debug |
All fields implement the trait | Yes — derive safely |
Some fields should be excluded from comparison | No — implement manually |
Custom Derive Macros (Procedural Macros)
Beyond the standard derives, Rust allows library authors to write their own
#[derive(...)] macros using procedural macros. Popular examples include:
- serde:
Serialize,Deserialize - thiserror:
Error— derivesstd::error::Errorwith formatted messages - clap:
Parser— derives a CLI argument parser - diesel:
Queryable,Insertable— derives database ORM traits - proptest:
Arbitrary— generates random test values
Writing your own procedural macros involves the proc-macro crate, syn, and
quote. See the Procedural Macros page for a complete walkthrough.
// thiserror example: derive Error with a formatted message
use thiserror::Error;
#[derive(Debug, Error)]
enum AppError {
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
#[error("Parse error at line {line}: {message}")]
Parse { line: usize, message: String },
#[error("Not found: {0}")]
NotFound(String),
}
fn find_config(name: &str) -> Result<String, AppError> {
if name.is_empty() {
return Err(AppError::NotFound(String::from("config name is empty")));
}
Ok(format!("config:{}", name))
}
fn main() {
match find_config("") {
Ok(cfg) => println!("{}", cfg),
Err(e) => println!("Error: {}", e), // Error: Not found: config name is empty
}
}All Standard Derivable Traits at a Glance
Trait | Enables | Requires |
|---|---|---|
Debug | {:?} and {:#?} formatting, dbg! macro | All fields: Debug |
Clone | .clone() deep copy | All fields: Clone |
Copy | Implicit bitwise copy, no move | All fields: Copy; also derive Clone |
PartialEq | == and != operators | All fields: PartialEq |
Eq | Total equality (reflexive), enables HashMap key | PartialEq; all fields: Eq |
PartialOrd | <, >, <=, >= operators | PartialEq; all fields: PartialOrd |
Ord | Total ordering, sort(), .min(), .max() | Eq + PartialOrd; all fields: Ord |
Hash | HashMap / HashSet key | Eq; all fields: Hash |
Default | Default::default() zero constructor | All fields: Default |