Deriving a trait can add a generic bound that the fields do not actually need. The resulting implementation is valid, but available for fewer type arguments than you may intend. When a wrapper fails a trait check, inspect the required bounds before changing the wrapped type.
A cloneable handle does not need cloneable contents
This complete example intentionally fails to compile:
use std::rc::Rc;
#[derive(Clone)]
struct Holder<T>(Rc<T>);
struct NotClone;
fn main() {
let original = Holder(Rc::new(NotClone));
let _duplicate = original.clone();
}
For this declaration, the derived Clone implementation requires T: Clone.
Holder<NotClone> therefore lacks the required implementation, and the call
produces E0599 on Rust 1.98.1. Defining the wrapper itself is legal: the
conditional implementation works when its bound is satisfied.
But Rc<T> can clone its handle without cloning T. If that is the intended
behavior for every Holder<T>, replace the derive with a manual implementation:
use std::rc::Rc;
struct Holder<T>(Rc<T>);
impl<T> Clone for Holder<T> {
fn clone(&self) -> Self {
Self(Rc::clone(&self.0))
}
}
struct NotClone;
fn main() {
let original = Holder(Rc::new(NotClone));
let duplicate = original.clone();
assert!(Rc::ptr_eq(&original.0, &duplicate.0));
assert_eq!(Rc::strong_count(&original.0), 2);
}
Both wrappers share the same contained value. Adding Clone to NotClone
would also satisfy the original derive, but it is unnecessary for handle
cloning. Choose the implementation according to the API you want to support.
A wrapper that stores T directly and clones it really does need T: Clone.
Separate added bounds from field requirements
The table shows specific forms checked on Rust 1.98.1. It is not a rule for every derive macro or every declaration shape.
| Declaration pattern | Derived requirement | Why a manual implementation may be broader |
|---|---|---|
Clone for a struct containing Rc<T> |
T: Clone |
Cloning an Rc handle does not clone T. |
Clone, Copy for a struct containing PhantomData<T> |
T: Clone for Clone; T: Copy for Copy |
The marker itself supports both traits without those bounds. |
Debug for a struct containing PhantomData<T> |
T: Debug |
Formatting the marker does not format a stored T. |
Default for a struct containing Vec<T> |
T: Default |
An empty vector needs no T value. |
PartialEq for a struct containing PhantomData<T> |
T: PartialEq |
Comparing markers does not compare T values. |
Removing a generic bound cannot make an unsuitable field implement a trait.
In particular, Rc<T> and Arc<T> are not Copy, even when T is. A struct
containing either cannot implement Copy; cloning their handles must account
for shared ownership. Shared references and PhantomData have different rules.
A marker can be Copy without T being Copy
A marker wrapper can implement both traits manually:
use std::marker::PhantomData;
struct Marker<T>(PhantomData<T>);
impl<T> Copy for Marker<T> {}
impl<T> Clone for Marker<T> {
fn clone(&self) -> Self { *self }
}
struct NotCopy;
fn require_copy<T: Copy>() {}
fn main() {
require_copy::<Marker<NotCopy>>();
let first = Marker::<NotCopy>(PhantomData);
let _second = first;
let _third = first;
}
Copy requires Clone too. For a Copy type, cloning should agree with copying
the value; *self is the canonical implementation. Clippy's
non_canonical_clone_impl checks this pattern and warns by default. Rustc
can accept other bodies, so this lint is not a compiler-enforced restriction
on every Clone implementation.
Ordinary copying does not call clone, and generic operations may use a Copy
path instead of cloning elements individually. Do not rely on cloning to run
bookkeeping whenever a Copy value is duplicated. This does not mean an
explicit clone() call can arbitrarily lose its observable side effects.
See the Clone contract.
An empty collection can default without element defaults
For a stack wrapper, a manual default can create an empty vector for any T:
struct Stack<T> {
items: Vec<T>,
}
impl<T> Default for Stack<T> {
fn default() -> Self {
Self { items: Vec::new() }
}
}
struct NoDefault;
fn main() {
let stack = Stack::<NoDefault>::default();
assert!(stack.items.is_empty());
}
Replacing this implementation with #[derive(Default)] adds a T: Default
requirement for this struct. The empty-vector operation itself does not need
that bound. A similar issue occurs with a struct field of type Option<T>.
Do not generalize to every derive
Derived Default for an enum with a selected unit variant is a counterexample to the claim that every generic parameter always gets the derived trait bound:
#[derive(Default)]
enum Maybe<T> {
#[default]
Empty,
Value(T),
}
struct NoDefault;
fn main() {
let empty = Maybe::<NoDefault>::default();
assert!(matches!(empty, Maybe::Empty));
let supplied = Maybe::Value(NoDefault);
assert!(matches!(supplied, Maybe::Value(_)));
}
This works without NoDefault: Default. The selected variant has no payload
to construct. Derive behavior depends on the trait and declaration; custom
procedural derives can choose their own rules as well.
When a derive is too restrictive, check the trait's requirements, the field types' implementations and the diagnostic's unsatisfied bounds. Write a small compile check using a type argument that lacks the disputed trait. If a manual implementation removes the bound, verify that it preserves the intended behavior, such as sharing an Rc allocation or constructing an empty collection.