16.5Deriving traits

Last updated June 13, 2026

For many common traits, the implementation is mechanical: there's an obvious correct version the compiler can write from your type's definition. #[derive(...)] is how you ask it to. You've used derive since lesson 10.7 for Debug; now that you know what a trait is, this lesson tours the derivable traits and what each one gives you. Think of it as the menu.

How derive works

#[derive(Trait)] above a type tells the compiler: "write the standard implementation of Trait for this type." It works field by field, deriving the trait for the whole struct or enum by combining its behavior on each field. That's why a struct can only derive a trait if all its fields already implement it; the derived implementation is built out of the fields' implementations.

#[derive(Debug, Clone, PartialEq)]
struct Point {
    x: f64,
    y: f64,
}

This one line gives Point three behaviors: printable with {:?}, cloneable with .clone(), and comparable with ==. Each would be tedious and error-prone to write by hand, and the compiler's version is exactly what you'd write anyway.

The tour

The traits you can derive, and what each unlocks:

Debug enables {:?} and {:#?} formatting (lesson 10.7) and makes the type usable with dbg! and assert_eq! failure messages. Derive it on essentially every type; the cost is nothing and the debugging payoff is constant.

Clone enables .clone(), an explicit deep copy (lesson 8.7). Derive it when you need independent copies of a value.

Copy makes the type copy implicitly on assignment instead of moving (lesson 8.5). It requires Clone too (#[derive(Copy, Clone)] together) and is only allowed when every field is Copy, so a struct containing a String can never be Copy. Derive it on small, all-Copy types like a Point of numbers.

PartialEq enables == and !=. Eq is a marker added on top (#[derive(PartialEq, Eq)]) that promises equality is "total", every value equals itself, which floats notably don't satisfy because NaN != NaN (lesson 4.5). So a type with an f64 field can derive PartialEq but not Eq.

PartialOrd enables the comparison operators <, >, <=, >= (the trait that finished largest in lesson 16.3). Ord adds total ordering on top (requires Eq and PartialOrd), needed to sort or to use as a sorted-collection key. For a struct, the derived ordering compares fields in declaration order, like comparing tuples.

Hash lets a type be used as a HashMap key (lesson 18.6).

Default provides ::default(), every field at its default value (lesson 10.6).

Key insight

derive is the compiler writing the obvious implementation so you don't. The rule of thumb behind which traits are derivable: a trait is derivable when "do it for each field and combine" is unambiguously the right implementation. Debug (show each field), Clone (clone each field), PartialEq (compare each field), Default (default each field), all fit. A trait like Display (lesson 16.6) is not derivable, because there's no obvious single way to present a type to humans, that one you must write.

A common derive set

In practice a few derives appear together so often they're almost reflexive. For a plain data type you'll frequently write:

#[derive(Debug, Clone, PartialEq)]
struct Config {
    name: String,
    retries: u32,
}

Debug for inspection, Clone for making copies, PartialEq so you can compare (and so assert_eq! works in tests, lesson 14.2). Add Default if a "zeroed" instance makes sense, Eq + Hash if it'll be a map key, Copy + Clone if it's small and all-Copy. You add derives as you discover you need the capability; the compiler's error messages tell you which trait is missing when you try to do something the type can't yet.

When you can't derive

Two situations stop a derive. First, a field doesn't implement the trait: you can't derive Clone for a struct whose field isn't Clone, because the field-by-field implementation has nothing to call. The fix is to make the field cloneable or implement Clone by hand. Second, the derived behavior isn't what you want: the mechanical version is correct but not appropriate, for instance two Color values that should compare equal if their RGB matches regardless of an attached name field. When the obvious implementation is wrong for your type, skip the derive and write impl Trait for Type by hand, exactly as you did for Summary in lesson 16.1. Derive is a convenience, never an obligation.

Quiz time

Question #1

Why can a struct derive a trait only if all its fields implement that trait?

Show solution

Because the derived implementation is built field by field: deriving Clone clones each field, deriving PartialEq compares each field, and so on. If a field doesn't implement the trait, there's nothing for the derived code to call on that field, so the derive can't be generated. The whole is assembled from the parts.

Question #2

Your struct has an f64 field. Can you derive PartialEq? Can you derive Eq? Why?

Show solution

You can derive PartialEq (floats support ==), but not Eq. Eq promises total equality, where every value equals itself, and floats violate that because NaN != NaN (lesson 4.5). So f64 implements PartialEq but not Eq, and a struct containing one inherits that limitation.

Question #3

When should you write a trait implementation by hand instead of deriving it?

Show solution

When the mechanical field-by-field version isn't what you want (e.g. equality that should ignore some field, or a custom ordering), or when the trait isn't derivable at all (like Display, which has no obvious automatic form). Also when a field doesn't implement the trait and you need custom handling. Derive when the obvious implementation is right; hand-write when it isn't.

Display was the one trait this lesson said you can't derive. The next lesson implements it by hand, the controlled, human-facing {} output you've wanted since lesson 10.7, and the Rust version of C++'s "overloading operator<<."