RFA-512 · Case file with fixtures · Case 484 of 694 · Compiler evidence
Rust Inherent Impls Need a Nominal Self Type
Inherent methods attach to one owned nominal type, not every possible T. Universal opt-in behaviour belongs in a trait; representation-specific behaviour belongs on a local wrapper.
- 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
- No local nominal declaration owns the universe represented by T, so a blanket inherent impl would inject methods across unrelated types and crates.
- First discriminating check
- Use an intentionally scoped local trait for generic capability or a local Wrapper<T> for representation and invariants, reviewing blanket-impl overlap.
I wrote impl<T> T to add identity to every type. Rust emitted E0118 because an inherent impl needs an eligible nominal self type, not a free type parameter standing for the whole universe.
The failing fixture expresses a common wish: attach a convenience method everywhere. Rust routes that design through traits.
Inherent methods need one owned identity
A nominal struct, enum, or union has a declaration that owns its inherent namespace. T inside impl<T> T can become types from any crate, primitive types, references, and many other forms.
No one local declaration owns that set. Allowing the impl would let every crate inject methods into every type and make method resolution depend unpredictably on dependencies.
The official E0118 page recommends a trait or wrapper.
The repaired fixture defines universal capability as a trait
The repaired fixture declares local trait Identity, gives its method a default body, and implements the trait for all T.
This blanket trait impl is coherent because the trait belongs to the current crate. Callers opt into method syntax by bringing the trait into scope.
The Sized bound is explicit because taking and returning Self by value requires a sized value in this design.
Trait methods have a visible owner
If two extension traits define identity, a call may become ambiguous, but fully qualified syntax can name the intended trait. This is more controlled than several crates contributing indistinguishable inherent methods.
I use a meaningful trait name and a small capability surface. A generic Helpers trait with dozens of methods increases collision risk and makes bounds unclear.
Trait ownership is both a coherence rule and an API-navigation tool.
A local wrapper is better for representation policy
Some methods should not apply to every T. If the abstraction owns caching, validation, or storage, I define Wrapper<T> and attach inherent methods to it.
Now the nominal type expresses when the behaviour exists. It can contain extra state and control construction. Users convert into or borrow through the wrapper deliberately.
I choose a blanket trait for capability and a wrapper for identity plus representation.
Bounds can narrow a blanket trait impl
impl<T: Display> Pretty for T applies only where the bound is satisfied. The trait declaration or method body can use the proven capability.
This remains a trait impl, not an inherent impl. I review overlap with other implementations because blanket impls can prevent later specialised implementations under coherence rules.
A universal one-line convenience may create a surprisingly broad compatibility commitment.
Aliases and references are not local nominal types
A type alias for T, a primitive, tuple, or reference does not create the owned nominal identity needed for arbitrary inherent methods. Even if the spelling is local, the underlying self type controls eligibility.
The Reference describes which implementation forms associate items directly with a type.
I inspect the resolved self type rather than its surface alias.
Universal identity is deliberately a teaching example
The fixture's identity method adds almost no production value; every owned value can already be returned from a normal helper. It exists to isolate E0118. A real blanket extension should earn its permanent place in method lookup.
Before publishing one, I ask whether a free generic function is clearer, whether the method name could collide, and whether the blanket impl prevents another crate from expressing a more specific relationship. Trait coherence turns a convenient one-line impl into a long-lived API commitment. I keep these traits narrow, sealed when appropriate, and backed by use cases rather than creating a universal utility namespace.
Generic inherent impls are valid on a nominal container
impl<T> Wrapper<T> is valid because Wrapper<T> remains one local nominal type across its instantiations. The parameter describes the wrapper; it does not become the self type by itself. This contrast is the shortest way I remember the rule.
My E0118 checklist
- Is the self type a specific nominal type or merely
T? - Does my crate own the type's inherent namespace?
- Is the behaviour truly meaningful for every admitted type?
- Would a local extension trait make capability explicit?
- Would a wrapper better own state or invariants?
- Which
Sizedor other bounds does the method require? - Could a blanket impl block future specialised implementations?
- Will fully qualified syntax resolve potential method collisions?
The core principle is that inherent behaviour attaches to an owned type identity. Behaviour spanning arbitrary types belongs in an explicitly owned trait with deliberate blanket-implementation scope.