Derive Macros
Reviewed & published by Brayan K
A derive macro is an attribute written above a type — like #[derive(Debug, Clone, PartialEq)] — that tells the compiler to auto-generate a standard implementation of those traits so you don't write the boilerplate yourself.
Part of the free Rust course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
In this lesson you'll learn what each common derivable trait generates, when you can and can't derive a trait (for example, why Copy needs all-Copy fields), and how the ordering and hashing traits unlock sorting and hash collections.
What You'll Learn in This Lesson
- What Debug, Clone, and PartialEq generate
- Why Copy needs all-Copy fields (and needs Clone)
- PartialEq vs Eq and PartialOrd vs Ord
- Deriving Hash + Eq for HashMap/HashSet keys
- Deriving Default for zero-value structs
- How derived Ord compares fields in order
1️⃣ Debug, Clone, and PartialEq
The three you'll reach for most often: Debug lets you print a value with {:?} (or pretty-print with {:#?}), Clone gives you a .clone() method for an independent copy, and PartialEq enables == and !=, comparing field by field.
// #[derive(...)] asks the compiler to auto-generate trait implementations.
// Debug enables {:?} printing; Clone enables .clone(); PartialEq enables ==.
#[derive(Debug, Clone, PartialEq)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let a = Point { x: 1, y: 2 };
// Debug -> the {:?} formatter prints the struct's fields.
println!("{:?}", a);
// {:#?} is the "pretty" multi-line Debug form.
println!("{:#?}", a);
// Clone -> make an independent copy.
let b = a.clone();
// PartialEq -> compare with == and != (compares field by field).
println!("a == b? {}", a == b);
println!("a == origin? {}", a == Point { x: 0, y: 0 });
}Each derive wrote real code for you: the {:?} output, the .clone() method, and the == comparison would otherwise be a dozen lines of hand-written impl blocks.
2️⃣ Copy: When You Can and Can't Derive It
Copy changes assignment semantics: a Copy type is duplicated instead of moved, so the original stays usable. The catch: you can only derive Copy if every field is also Copy, and Copy always requires Clone, so they're derived together.
// Copy means a value is duplicated implicitly on assignment (no move).
// You can only derive Copy if EVERY field is itself Copy. Copy requires Clone.
#[derive(Debug, Clone, Copy)]
struct Rgb {
r: u8,
g: u8,
b: u8,
}
fn paint(color: Rgb) {
println!("painting with {:?}", color);
}
fn main() {
let red = Rgb { r: 255, g: 0, b: 0 };
// Because Rgb is Copy, passing it does NOT move it — red stays usable.
paint(red);
paint(red); // still valid: it was copied, not moved
println!("original still here: {:?}", red);
// A plain bitwise copy on assignment, too.
let also_red = red;
println!("also_red: {:?}", also_red);
println!("red: {:?}", red);
}3️⃣ Ordering and Hashing: Eq, Ord, Hash
To sort a type you derive the ordering set PartialEq, Eq, PartialOrd, Ord; to use it as a key in a HashMap or HashSet you derive Hash (plus Eq). Derived Ord compares fields in declaration order, so field order is meaningful.
use std::collections::HashSet;
// Ordering and hashing: PartialOrd/Ord enable comparisons and sorting;
// Hash + Eq let a type be a key in a HashSet or HashMap.
// Derived Ord compares fields in declaration order (lexicographic).
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct Version {
major: u32,
minor: u32,
}
fn main() {
let mut versions = vec![
Version { major: 1, minor: 4 },
Version { major: 1, minor: 0 },
Version { major: 2, minor: 1 },
Version { major: 1, minor: 4 },
];
// Ord makes sort() work; it orders by major, then minor.
versions.sort();
println!("sorted: {:?}", versions);
// PartialOrd gives < <= > >=.
let a = Version { major: 1, minor: 0 };
let b = Version { major: 1, minor: 4 };
println!("a < b? {}", a < b);
// Hash + Eq let Version live in a HashSet (which de-dupes).
let mut set: HashSet<Version> = HashSet::new();
for v in versions {
set.insert(v);
}
println!("unique count: {}", set.len());
}Note Eq and Ord are derivable here only because every field is an integer. If a field were an f64, you could derive PartialEq/PartialOrd but not Eq/Ord, because NaN breaks total ordering.
Your turn. Fill in the blank marked ___, then run it.
// 🎯 YOUR TURN — add the derives so this Config prints, defaults, and compares.
#[derive(Debug, ___, PartialEq)] // 👉 replace ___ with Default (it is what gives Config::default())
struct Config {
verbose: bool,
retries: u32,
name: String,
}
fn main() {
// Default::default() fills every field with its type's default value.
let c = Config::default();
println!("{:?}", c); // needs Debug
println!("is default? {}", c == Config::default()); // needs PartialEq
}
// ✅ Expected output:
// Config { verbose: false, retries: 0, name: "" }
// is default? trueMini-Challenge: A Sortable Card
Derive the right traits so a deck of cards can be sorted and the highest found. Run it with cargo run and check the output.
// 🎯 MINI-CHALLENGE: a sortable playing card.
//
// 1. Define struct Card { rank: u8, suit: u8 } and derive the traits it needs:
// Debug (to print), Clone + Copy (small, all-Copy fields), and the full
// ordering set: PartialEq, Eq, PartialOrd, Ord (so it can be sorted).
// 2. In main, build a Vec of four cards, call .sort() on it, and print it.
// 3. Then print the highest card with hand.iter().max().unwrap().
// (Derived Ord compares rank first, then suit.)
//
// ✅ Example output:
// [Card { rank: 3, suit: 1 }, Card { rank: 7, suit: 3 }, Card { rank: 10, suit: 0 }, Card { rank: 10, suit: 2 }]
// highest: Card { rank: 10, suit: 2 }
fn main() {
// your code here
}Common Errors (and the fix)
- ❌ `Point` doesn't implement `Debug` — you used {:?} without deriving it. ✅ Fix: add #[derive(Debug)].
- ❌ the trait `Copy` may not be implemented for this type — a field (often a String) isn't Copy. ✅ Fix: derive only Clone and clone explicitly.
- ❌ the trait bound `f64: Eq` is not satisfied — you derived Eq/Ord with a float field. ✅ Fix: use PartialEq/PartialOrd only.
- ❌ `Card` cannot be used as a HashMap key — missing Hash or Eq. ✅ Fix: derive both Hash and Eq.
Pro Tips
- 💡 Almost always derive Debug: it costs nothing and makes every value printable for logging and tests.
- 💡 Order matters for Ord: declare the most-significant field first, since comparison goes top to bottom.
- 💡 Hand-write only when needed: if the standard impl is fine, derive it; reserve manual impl blocks for custom logic.
📋 Quick Reference — Derivable Traits
| Derive | Gives you |
|---|---|
| Debug | {:?} printing |
| Clone / Copy | .clone() / implicit copy |
| PartialEq / Eq | == and != / total equality |
| PartialOrd / Ord | comparisons / sort() |
| Hash | HashMap / HashSet keys |
| Default | Type::default() |
🎉 Lesson Complete!
- ✅ #[derive(...)] auto-generates standard trait impls
- ✅ Debug prints, Clone copies, PartialEq compares
- ✅ Copy needs all-Copy fields and requires Clone
- ✅ Eq/Ord need non-float fields; floats only get the Partial versions
- ✅ Derived Ord compares fields in declaration order
Practice quiz
What does #[derive(...)] do?
- Renames a type
- Imports a module
- Tells the compiler to auto-generate a standard trait implementation
- Marks code as unsafe
Answer: Tells the compiler to auto-generate a standard trait implementation. It generates a standard impl of a trait for your type so you don't write the boilerplate by hand.
Which derive lets you print a value with {:?}?
- Debug
- Display
- Clone
Answer: Debug. Debug enables {:?} (and pretty {:#?}) formatting.
Which derive enables comparing values with == and !=?
- Ord
- Hash
- Copy
- PartialEq
Answer: PartialEq. PartialEq generates == and !=, comparing field by field.
Why might #[derive(Copy)] fail for a struct with a String field?
- String is too short
- String owns heap data, so it isn't Copy
- Copy needs Debug
- String can't be a field
Answer: String owns heap data, so it isn't Copy. Copy requires every field be Copy; String owns heap data, so the struct can only be Clone.
Copy can only be derived alongside which other trait?
- Clone
- Debug
- Default
- Hash
Answer: Clone. Copy always requires Clone, so they are derived together: #[derive(Clone, Copy)].
Why does f64 implement PartialEq but not Eq?
- f64 is not a number
- Eq is deprecated
- NaN != NaN breaks full equivalence
- f64 has no bits
Answer: NaN != NaN breaks full equivalence. Eq requires every value equal itself, but NaN != NaN, so floats only get PartialEq/PartialOrd.
To use a struct as a key in a HashMap or HashSet, which traits must it derive?
- Debug and Clone
- Hash and Eq
- Ord and Copy
- Display and Default
Answer: Hash and Eq. Hash (plus Eq) lets a type be a hash-collection key.
In what order does derived Ord compare a struct's fields?
- Alphabetically by field name
- Random order
- By field size
- In declaration order, lexicographically
Answer: In declaration order, lexicographically. Derived Ord compares the first declared field, then the second on ties, like sorting words by letters.
Which derive set is needed so a type can be sorted with .sort()?
- Just Debug
- PartialEq, Eq, PartialOrd, Ord
- Clone and Copy
- Hash only
Answer: PartialEq, Eq, PartialOrd, Ord. sort() needs a total order, supplied by deriving the full PartialEq, Eq, PartialOrd, Ord set.
Which derive provides a Type::default() value with zeroed/empty fields?
- Debug
- Clone
- Default
- Ord
Answer: Default. Default generates Type::default(), setting each field to its type's default value.
Continue this course
- Previous: Associated Types & Trait Bounds
- Next: Operator Overloading (std::ops) — Implement Add, Mul and Index for your own types
- Quick reference: Rust cheat sheet › Traits & Generics
Frequently asked questions
What exactly does #[derive(...)] do?
It is an attribute that tells the compiler to generate a standard implementation of a trait for your type, so you don't have to write it by hand. For example, #[derive(Debug)] writes the code that lets {:?} print your struct's fields, and #[derive(PartialEq)] writes a field-by-field equality check. Behind the scenes each derivable trait has a procedural macro that produces the impl block at compile time. You only get the boilerplate-free version of common traits; for custom behaviour you still write the impl yourself.
Why can't I derive Copy for my struct?
Copy can only be derived when every field of the type is itself Copy, because Copy means the value can be duplicated with a simple bitwise copy and no extra work. Types that own heap data, like String and Vec, are not Copy (duplicating them needs an allocation), so a struct containing a String cannot be Copy. The fix is usually to derive Clone instead and call .clone() explicitly, which is allowed for heap-owning types. Also note Copy requires Clone, so you always derive them together: #[derive(Clone, Copy)].
What is the difference between PartialEq and Eq, or PartialOrd and Ord?
PartialEq provides == and != and is enough for most types. Eq is a marker that says equality is a full equivalence relation — every value equals itself — which is not true for floating-point numbers because NaN != NaN. So f64 implements PartialEq but not Eq. Similarly PartialOrd gives the comparison operators where some pairs might be incomparable, while Ord guarantees a total order and is what sort() and BinaryHeap need. Derive Eq and Ord only when none of your fields are floats; otherwise stick to the Partial versions.
In what order does derived Ord compare a struct's fields?
Derived Ord and PartialOrd compare fields in the order they are declared, lexicographically. It compares the first field; if those are equal it moves to the second, and so on — exactly like sorting words by their letters. So for struct Version { major, minor }, versions sort by major first and only use minor to break ties. If you need a different ordering, declare the fields in priority order or write the Ord impl by hand instead of deriving it.