11.7 Deriving Common Traits: Debug, Clone, PartialEq
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
PartialEqimplementation, you only want to compare anidfield and ignore everything else. You’d need to implement that by hand. - Performance: The derived
Clonemight 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
Clonefor 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.