RFA-491 · Case file with fixtures · Case 463 of 694 · Compiler evidence
A Rust Trait Method Impl Must Keep the Generic Parameter List
A generic trait method promises one implementation that works for every admitted type. Replacing its method generic with one concrete input narrows the contract and is not a matching 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 trait lets each caller choose any admitted T, while the concrete impl signature narrows that caller-selected family to one input type.
- First discriminating check
- Compare method-level type and const parameters separately from trait parameters, then preserve their count and bounds or redesign where the type choice belongs.
I declared Convert::convert<T> but implemented it with value: u32 and no method-level type parameter. Rust emitted E0049. The concrete input looked like a normal specialization to me at first, but it changed who gets to choose the type.
The failing fixture captures this difference. The trait says the caller may choose any T satisfying ToString. The impl says only u32 is accepted. Those are different APIs.
Genericity is part of the method identity
The official E0049 explanation describes a mismatch in type or const parameter count. Rust compares the generic shape of the implementation method with the declaration, not only its name and final return type.
For fn convert<T>(...), T belongs to each call. The implementor must provide a method valid for all permitted choices. Replacing T with u32 is not filling the same slot with a faster body; it is narrowing the slot.
This is why the repair restores <T: ToString> on the impl method.
Method generics and trait generics choose at different times
trait Convert { fn convert<T>(...); } lets every call select T. By contrast, trait Convert<T> { fn convert(...); } selects T as part of the trait implementation relationship.
That distinction matters when I need one converter for many inputs versus separate implementations for separate input families. Moving the generic parameter to the trait is an API redesign, not a syntax-only fix. It changes bounds, inference, implementation overlap, and how callers name the capability.
I first decide where the choice belongs, then make the declaration and implementation match.
The repaired method keeps the caller's freedom
The repaired fixture implements convert<T: ToString> exactly and calls it with an integer. The same implementation could accept a string-like value or another type implementing ToString.
The body must use only operations guaranteed by the bounds. If it tries a u32-specific method, the next compiler error will correctly reveal that the trait promise is wider than the body.
I find this useful: E0049 catches the signature drift, then ordinary bound checking catches an implementation that is not truly generic.
A concrete-only capability needs another contract
Sometimes only u32 support is intentional. In that case I can put u32 on a trait parameter, use an associated input type, or expose an inherent method with a concrete signature.
Each option assigns selection differently. A trait parameter may permit several implementations of one trait for different types, subject to coherence. An associated type gives each implementation one selected input. An inherent method does not promise generic interoperability at all.
I avoid pretending a generic trait method is specialised when stable Rust expects the full contract.
Const parameters count too
E0049 also covers const generic parameters. A trait method such as fn take<const N: usize>([u8; N]) promises support across admitted lengths. An impl that hard-codes [u8; 16] has removed one const parameter and narrowed the method.
The exact names of generic parameters may differ, but their number, kind, order, and effective bounds must form a compatible declaration. I compare the signatures structurally rather than copying names mechanically.
This often appears during refactoring
An implementation may be written before a trait becomes generic. Another common case is dependency evolution: a library generalises a method, while a downstream impl still has the old concrete signature.
I inspect the definition rustc actually resolved, including feature-gated versions. Then I check every impl and macro template. Editing only the first emitted error can leave other generated variants stale.
For public traits, changing a concrete method into a generic one can also affect dyn compatibility. I review object users separately instead of assuming the change is source-compatible because the names stayed equal.
My E0049 checklist
- How many type and const parameters does the trait method declare?
- Are they method parameters or parameters of the trait itself?
- Who should choose each parameter: caller or implementor?
- Did the impl replace a generic input with one concrete type?
- Do the bounds allow the operations used by the method body?
- Has the resolved dependency or enabled feature changed the trait?
- Does a derive macro still emit the old generic shape?
- Would redesigning the parameter location affect coherence or trait objects?
The core principle is that a generic method promises caller choice. An implementation cannot quietly take that choice away; it must preserve the method's generic shape or implement a differently designed contract.