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

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? true

Mini-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)

Pro Tips

📋 Quick Reference — Derivable Traits

DeriveGives you
Debug{:?} printing
Clone / Copy.clone() / implicit copy
PartialEq / Eq== and != / total equality
PartialOrd / Ordcomparisons / sort()
HashHashMap / HashSet keys
DefaultType::default()

🎉 Lesson Complete!

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
  • Print

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

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.