Let’s be honest: writing fmt::Debug for every struct by hand is a special kind of masochism. You and I have better things to do. This is where the #[derive] attribute swoops in like a superhero, automatically generating trait implementations so you don’t have to. It’s Rust’s way of saying, “I got you, fam.”

We’re going to look at the three traits you’ll #[derive] more than any others: Debug, Clone, and PartialEq. They’re the holy trinity of making your structs useful and non-infuriating.

The Magical Incantation: Using #[derive]

The syntax is beautifully simple. You just slap the #[derive(...)] attribute above your struct (or enum) and list the traits you want. The compiler does the rest.

#[derive(Debug, Clone, PartialEq)]
struct Satellite {
    name: String,
    velocity: f64, // m/s
}

That’s it. With those seven words, we’ve generated a ton of useful code. Let’s break down what each one gives us.

Debug: Your Best Friend in a Crisis

The Debug trait (std::fmt::Debug) is non-negotiable. It’s what lets you print your struct for debugging purposes using {:?} or {:#?} in println!. If you’ve ever tried to print a struct without it, you’ve met the compiler’s icy wrath. Deriving it gives you a sensible, field-by-field output.

let hubble = Satellite {
    name: String::from("Hubble"),
    velocity: 7.5,
};

println!("Hubble is: {:?}", hubble);
// Prints: Hubble is: Satellite { name: "Hubble", velocity: 7.5 }

Why you should always derive it: Because debugging without it is like trying to find a needle in a haystack while blindfolded. It’s the first thing you add to any struct. I’m not kidding. Do it now.

Clone: For a Full-Value Copy

Sometimes, you need a full, separate copy of a value. This is not a reference (&), it’s a deep copy. The Clone trait (std::clone::Clone) defines this ability. The derived implementation simply calls .clone() on each field of your struct, so all its fields must also be Clone.

let hubble_copy = hubble.clone();
println!("Original: {:?}, Copy: {:?}", hubble, hubble_copy);

This is different from Copy. Copy is a marker trait for types that are so cheap to copy that the compiler can just do it implicitly (like integers). Clone is explicit—you have to call .clone(). For structs containing a String (which is not Copy), you want Clone.

PartialEq: The Art of Comparison

Trying to use == or != on your struct without this will get you another helpful compiler error. Deriving PartialEq (std::cmp::PartialEq) implements these operators for you. It compares your struct field-by-field, which is exactly what you want 99% of the time.

let hubble_fake = Satellite {
    name: String::from("Hubble"),
    velocity: 7.5,
};

println!("Are they equal? {}", hubble == hubble_fake); // Prints: true

Notice it’s PartialEq, not Eq. The difference is philosophical. PartialEq allows for values that cannot be considered equal to themselves (like the floating-point NaN!). Eq is a marker trait you can only use if your type fully implements total equality. Since our Satellite has an f64 (velocity), which is not Eq because NaN != NaN, we can only derive PartialEq. If all your fields were integers, strings, or other Eq types, you could also #[derive(Eq)] after PartialEq.

The Golden Rule: Your Fields Must Also Implement the Trait

This is the most common “gotcha.” The derived implementation isn’t magic; it’s just generating the obvious code. If you try to derive Clone for a struct containing a field that doesn’t implement Clone, the derivation will fail. The compiler’s error message is actually pretty good here—it will point directly to the offending field.

// This will NOT compile!
#[derive(Clone)]
struct Problematic {
    data: Rc<i32>, // Rc<T> is famously *not* Clone (it's .clone() is in a different way)
}

The fix? Either use a type that implements the trait (like Arc<T> instead of Rc<T> for thread-safe Clone), or manually implement the trait yourself for more nuanced behavior.

When Not to Derive

Deriving is brilliant, but it’s not always right.

  • Custom Logic: Maybe for your PartialEq implementation, you only want to compare an id field and ignore everything else. You’d need to implement that by hand.
  • Performance: The derived Clone might do more cloning than you absolutely need. A manual implementation could be more efficient.
  • Un-cloneable Fields: As mentioned above, if a field can’t be cloned, you can’t derive Clone for the whole struct.

But for the vast majority of your data-carrying structs, #[derive(Debug, Clone, PartialEq)] is the correct incantation. It saves you time, prevents bugs, and makes your types a pleasure to work with. It’s one of those small quality-of-life features that makes Rust feel thoughtfully designed, not just academically interesting.