Skip to content
← Back to explainers

Rust explainer

When derived trait bounds are too restrictive

About 4 min to read. Allow extra time to work through examples.

Inspect the bounds a derived implementation requires, then write a manual implementation when the wrapper can support more types.

This article uses Rust. You can make it your practice mode to browse related content across the site.

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.

step 24 of 24

Generics and Traits

You've reached the last step

Revisit the track to review its lessons and exercises, or explore another topic.

Explore more tracks