RFA-635 · Case file with fixtures · Case 607 of 694 · Compiler evidence
derive Applies to Data Types, Not Functions or Associated Items
Derive describes how a data type implements a trait from its fields and variants. Move it to the intended type, implement behaviour normally, and inspect macro attachment after refactors.
- 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
- A refactor, cfg branch, or generated token sequence left the attribute attached to a non-data item after its intended target moved.
- First discriminating check
- Inspect the next expanded item, move derive only to the data type that owns the trait, or remove it when no such contract exists.
#[derive(Clone)] asks a macro to generate a Clone implementation from the shape of a data type. A function has no fields or variants from which to derive that implementation. The failing fixture attaches derive to refresh and receives E0774.
Attributes attach to the next item
Whitespace does not separate an attribute from the item following it. During a refactor, deleting or moving a struct can leave its derive directly above a function, trait member, or module. The error points at the attribute, but the cause may be an earlier edit that changed item order.
The official E0774 explanation limits derive to structs, enums, and unions. The Reference explains derive attributes as a way to generate implementations for supported traits.
I inspect both the attribute and the actual next syntax item, including code produced by cfg and macros.
Move the derive only when that type owns the trait
The repaired fixture creates a Refresh type and derives Clone on it. That is appropriate because the type, not the function, participates in the trait.
In production I do not invent a marker type merely to preserve an accidental attribute. I first find the intended data declaration. If no type needs cloning, the correct repair is simply to remove the derive.
Functions already have callable item and pointer semantics; cloning a function item is not the data-model problem derive solves. If I need a clonable operation with captured configuration, a closure in an Arc, a strategy type, or a trait object may express the architecture.
Derived behaviour is a real semantic choice
Deriving a trait means every relevant field must support it and the generated behaviour follows field or variant structure. Clone can duplicate handles, counters, channels, or reference-counted ownership with domain effects that differ from making an independent resource.
PartialEq, Hash, and ordering traits determine collection and cache behaviour. Default chooses a baseline value. Debug may expose sensitive fields. I review these semantics rather than treating derive as boilerplate.
The Book’s derivable traits appendix summarises standard derives. Manual implementations are better when the domain contract intentionally differs from structural behaviour.
Procedural derives operate on token input
A custom derive receives a data item and generates additional tokens. Helper attributes declared by that derive are interpreted only in the expected context. Placing them on a function may produce an unknown-attribute diagnostic or misleading macro error instead of E0774.
I keep custom derive input small, inspect expanded code when diagnostics point far away, and use compile-pass and compile-fail fixtures. Tests cover structs, tuple structs, unit forms, enums, generics, lifetimes, and invalid helper combinations.
Macros that emit a derive followed by an optional item must ensure cfg removal cannot change which item receives the attribute. Generating the attribute and target in one token group avoids accidental attachment.
Public API changes require review
Adding an implementation can affect method resolution and downstream generic code. Removing one is plainly breaking. Changing fields can alter automatically derived equality, ordering, hashing, cloning cost, or debug output even if the trait remains implemented.
For stable types I document non-obvious behaviour and write contract tests. Snapshotting debug text is appropriate only when its format is promised; otherwise I avoid turning an implementation detail into a compatibility burden.
Security review checks whether derived Debug prints tokens or personal data and whether derived serialization exposes internal fields. Serialization derives are external schema decisions beyond ordinary Rust trait convenience.
My E0774 checklist
- Which exact item follows the derive after cfg and macro expansion?
- Is it a struct, enum, or union that can own generated trait implementations?
- Was the intended type moved or removed during a refactor?
- Does the domain actually want the derived behaviour?
- Do all fields satisfy the trait and have acceptable cost or disclosure?
- Would a manual implementation state the contract more accurately?
- Can macro output or cfg cause the attribute to attach to a different item?
- Do compile fixtures cover valid and invalid custom derive inputs?
The core principle is that derive creates behaviour from data shape. E0774 catches an attribute with no suitable data item. I restore the correct attachment, then review the generated trait contract as carefully as hand-written code.