#[derive(Clone)] looks like magic. It is a macro, and once you know the
specific impl it emits, an entire category of confusing errors becomes
obvious. This is the point where you stop treating derive as a language
feature and start treating it as code somebody wrote.
The rule
For a generic type, #[derive(Trait)] emits an impl with T: Trait on
every type parameter. Not on the parameters the impl actually needs — on
all of them.
#[derive(Clone)]
struct Holder<T>(Rc<T>);
expands to, in effect:
impl<T: Clone> Clone for Holder<T> {
fn clone(&self) -> Self { Holder(self.0.clone()) }
}
Look at that bound and then look at the field. Rc<T> implements Clone
unconditionally — cloning an Rc bumps a refcount and never touches the
T. The body of that generated clone does not need T: Clone at all. The
bound is there anyway, because the derive macro sees the syntax of your
struct, not the semantics of Rc.
What that costs you
struct NotClone;
let h = Holder(Rc::new(NotClone));
let _h2 = h.clone();
error[E0599]: the method `clone` exists for struct `Holder<NotClone>`,
but its trait bounds were not satisfied
|
4 | struct NotClone;
| --------------- doesn't satisfy `NotClone: Clone`
...
14 | struct Holder<T>(Rc<T>);
| ---------------- method `clone` not found for this struct because it
| doesn't satisfy `Holder<NotClone>: Clone`
...
25 | let _h2 = h.clone();
| ^^^^^ method cannot be called on `Holder<NotClone>`
| due to unsatisfied trait bounds
|
note: trait bound `NotClone: Clone` was not satisfied
|
13 | #[derive(Clone)]
| ----- in this derive macro expansion
= help: consider manually implementing the trait to avoid undesired bounds
help: consider annotating `NotClone` with `#[derive(Clone)]`
Two things about that diagnostic are worth dwelling on.
It points at the use site. The error is on line 25, where you called
clone, not on line 13 where the mistake actually is. In a real codebase the
derive is in one file, the type parameter is chosen in another, and the call
is in a third. Without the rule in your head this is genuinely hard to debug.
The suggested fix is the wrong one. rustc says “consider annotating
NotClone with #[derive(Clone)]“, which would work but is backwards: you
would be making an unrelated type cloneable to work around a bound your
wrapper never needed. The right fix appears one line earlier, in the
understated help: consider manually implementing the trait to avoid undesired bounds.
💡Here is a wrapper that holds no T at all: #[derive(Clone, Copy)] struct W<T>(PhantomData<T>);. Predict what happens for W<NotClone>, then explain it.
click to reveal
It fails, with the same E0599:
the method `clone` exists for struct `W<NotClone>`,
but its trait bounds were not satisfied
note: trait bound `NotClone: Clone` was not satisfied
type parameter would need to implement `Clone`
W<NotClone> contains zero bytes of NotClone. PhantomData<T> is a
zero-sized marker; there is nothing to clone. The derive still emits
impl<T: Clone> Clone for W<T>, because the rule is purely syntactic: one
parameter, one bound.
This is the cleanest possible demonstration that the derive is not reasoning
about your type. It is a macro doing a textual transformation, and it has no
idea PhantomData is special.
The fix is four lines:
impl<T> Clone for W<T> {
fn clone(&self) -> Self { *self }
}
impl<T> Copy for W<T> {}
Now W<NotClone> is Clone and Copy, which is correct, because copying a
zero-sized marker cannot possibly require anything of T. Verified on
rustc 1.95: with those two impls, assert_clone::<W<NotClone>>() and
assert_copy::<W<NotClone>>() both compile.
Hand-writing the impl: one trap
Notice the body above is *self, not W(PhantomData). That is not a style
choice, and clippy will stop you:
error: non-canonical implementation of `clone` on a `Copy` type
|
8 | fn clone(&self) -> Self { W(PhantomData) }
| ^^^^^^^^^^^^^^^^^^ help: change this to: `{ *self }`
|
= note: `-D clippy::non-canonical-clone-impl` implied by `-D warnings`
clippy::non_canonical_clone_impl is suspicious/default-on, and the reasoning
is sharp: for a Copy type the compiler is entitled to duplicate values by
memcpy without calling clone at all. So a clone that does anything other
than *self is a lie — it will sometimes run and sometimes be skipped,
and you will never find out which. If your Clone needs to do work, your type
must not be Copy.
Which derives have this problem
All of them, in the same way. Some hurt more than others:
| derive | emitted bound | typically wrong when |
|---|---|---|
Clone |
T: Clone |
the field is Rc<T>, Arc<T>, PhantomData<T>, &T |
Copy |
T: Copy |
same |
Debug |
T: Debug |
the field is PhantomData<T> or a marker |
Default |
T: Default |
the field is a Vec<T>, Option<T> or any empty collection |
PartialEq |
T: PartialEq |
the field is PhantomData<T> |
The Default row is the one you are most likely to hit in ordinary code. A
Stack<T> { items: Vec<T> } has a perfectly good empty default for every T,
because Vec::new() needs nothing — but #[derive(Default)] would restrict
it to T: Default anyway. That is exactly why the generic-stack problem
earlier in this track asks you to write
impl<T> Default for Stack<T> {
fn default() -> Self { Self::new() }
}
by hand.
What about “perfect derive”?
You will find discussion of perfect derive — a hypothetical smarter derive
that emits Rc<T>: Clone instead of T: Clone, i.e. bounds on the field
types rather than on the parameters. It would fix everything above.
Do not plan around it. It has been discussed for years, it is not stable, and it is not obviously a free win:
- It would leak your private field types into your public API’s bounds, so changing a private field could break downstream callers — a semver hazard that does not exist today.
- It interacts badly with inference in some cases, because the bound is no longer visible from the type’s declaration.
- It cannot be turned on by default without breaking existing code, so it would need an opt-in attribute, which is roughly as much typing as writing the impl.
The practical position for now: derive by default, and hand-write the impl the moment a type parameter appears in a position that does not really need the bound.
Two neighbouring lints
-
clippy::derived_hash_with_manual_eq— deny by default. DerivingHashwhile hand-writingPartialEqalmost always breaks thea == b implies hash(a) == hash(b)contract, and the failure mode is aHashMapkey that silently cannot be found. -
clippy::derive_partial_eq_without_eq— nursery, allow by default, so it will not fire under this course’s gate. Its advice is still reasonable: if a type has no floats in it, addEqand unlockHashMapandBTreeMapkeys.
The rule, one line
#[derive(Trait)] bounds every type parameter with Trait, whether the impl
needs it or not. When the answer is “it does not”, write the impl yourself —
and if the type is Copy, make clone return *self.