Mehdi Akiki
Rust Failure Atlas / Language and diagnostics

RFA-436 · Case file with fixtures · Case 408 of 694 · Compiler evidence

Why derive Cannot Be Applied to a Rust Associated Type

derive belongs on structs, enums, and unions whose fields and variants a macro can inspect. Require Clone with an associated-type bound, and let each concrete implementing type provide or derive its own implementation.

Reviewed
Rust
Rust 1.98.1, edition 2024
Targets
all Rust targets
Profiles
dev, release, test

Direct answer

What this Rust failure means

Why it happens
The associated type has no fields, variants, or concrete identity at the trait declaration, so the macro has no data type for which it could generate Clone.
First discriminating check
Put Clone on the associated-type bound when universal, or on one consumer's where clause, and derive Clone on each concrete data type where declared.

I wanted every implementation's associated item type to be cloneable, so I put #[derive(Clone)] above type Item in the trait. Rust rejected the attribute with E0774.

The failing fixture shows the exact placement. An associated type is a placeholder selected by each implementation; it is not a concrete data declaration for which derive can generate code.

derive needs a data definition to inspect

The derive attribute generates implementations for structs, enums, and unions. A derive macro can inspect fields or variants and produce code for that declared type.

A trait line such as type Item; has no fields, variants, size, or concrete identity. Different implementors can choose Vec<u8>, String, a reference, or a domain record.

There is therefore no single type on which Rust could place the generated Clone implementation.

Use a bound to require capability

The repaired fixture writes:

trait Source {
    type Item: Clone;
}

This says every concrete associated type selected by an implementation must already implement Clone. The Bytes implementation chooses Vec<u8>, which satisfies that bound.

The trait defines the requirement; the concrete type defines how cloning works.

Derive the concrete type where it is declared

If an implementation uses a custom record, I put #[derive(Clone)] on that record:

#[derive(Clone)]
struct Event {
    id: u64,
    payload: Vec<u8>,
}

Then type Item = Event meets the associated-type bound. The derive can see every field and add necessary bounds.

This also keeps generated behaviour close to the data representation it depends on.

A bound affects every implementation

Adding type Item: Clone is an API decision. It rejects sources whose items represent unique resources that should only move, such as certain guards or handles.

Before adding the bound, I ask where cloning is actually used. If only one consumer needs it, that consumer can add a where clause:

fn duplicate<S: Source>(source: S)
where
    S::Item: Clone,
{}

This keeps the base trait open to non-cloneable items while making the specialised operation's requirement visible.

Clone may carry real cost

Requiring Clone does not mean copying is cheap. A vector clone can allocate and copy every element; an Arc clone changes a reference count; a domain clone may duplicate substantial state.

I avoid using the bound merely to simplify ownership in an algorithm. Borrowing, iteration by reference, moving, or restructuring state may preserve a more honest performance contract.

Associated types choose one type per implementation

The Reference on associated types describes them as type aliases associated with another item and specified by implementations.

If a source must produce several item types, a generic trait parameter or a generic associated type may model the relationship differently. Adding derive syntax cannot resolve that design question.

Other attributes have target rules too

E0774 is a useful reminder that attributes are not free-floating annotations. Each attribute declares where it is valid and what syntax it transforms or controls.

When a macro attribute fails on an unexpected item, I check its documented targets. Moving it blindly to the nearest struct can generate behaviour on the wrong abstraction.

For custom procedural macros, I provide diagnostics that state accepted item kinds and point at the attribute span. The Atlas began partly from the problem of macro errors pointing at unhelpful places; good target validation is a direct improvement.

Supertraits do not replace associated-type bounds

Making Source: Clone would require the source object itself to be cloneable, not Source::Item. These capabilities are independent. I place each bound on the value that must provide it and add a small generic function that calls .clone() on that exact value. This catches a surprisingly common review error where a valid bound is attached to the wrong level of the abstraction.

My E0774 checklist

  • Is derive attached to a struct, enum, or union?
  • Am I trying to generate behaviour or require a capability?
  • Should the trait carry an associated-type bound?
  • Is the bound universal or needed by one consumer only?
  • Where is the concrete associated type declared?
  • What cost and ownership semantics does Clone introduce?
  • Does a custom attribute support this item kind?

The core principle is that generation and constraint are different tools. derive generates an implementation from one concrete data declaration. An associated type has no concrete representation at the trait site, so I express required behaviour as a bound and implement or derive it where the real type is defined.