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.