RFA-493 · Case file with fixtures · Case 465 of 694 · Compiler evidence
A Rust Trait Method Cannot Be Implemented as a Static Function
A receiver determines whether a trait item operates on an instance. Removing self changes the item from a method into an associated function, so it cannot fill the same trait slot.
- 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
- Removing self changes the associated item's call identity and disconnects it from the instance state promised by the trait method.
- First discriminating check
- Compare the exact receiver form first, restore the instance relationship, and keep any useful static helper as a separately named inherent function.
I declared Reset::reset(&mut self) but implemented fn reset() with no receiver. Rust emitted E0186 because these two items only share a name. One is an instance method and the other is a static associated function.
The failing fixture makes the missing receiver obvious. In a larger impl, this often happens after copying an inherent helper or after a trait changes from constructor-like behaviour to stateful behaviour.
The receiver decides how the item is called
A function whose first parameter is self, &self, &mut self, or another permitted explicit receiver form is a method. It can participate in method-call syntax such as counter.reset().
A function with no receiver is called through a type or trait path, such as Counter::reset(). There is no instance passed automatically.
The official E0186 explanation requires the implementation to use the same receiver-bearing signature as the trait. Adding an unrelated static function cannot satisfy the method promise.
The repaired impl restores instance state
The repaired fixture accepts &mut self, changes the counter, and checks the result. This demonstrates why the distinction is semantic, not ceremonial: reset needs access to one existing object's state.
If the operation creates a new default value instead, an associated function such as fn reset_value() -> Self may be sensible, but the trait must declare that function shape. I do not change only the impl side.
Receiver mutability is part of the capability
&self permits shared access, &mut self permits exclusive mutation, and self consumes the value. These choices communicate ownership behaviour to every caller and every implementation.
Removing the receiver altogether does not merely weaken or strengthen access. It removes the connection to an instance. Changing &self to &mut self is also not automatically compatible, even though both are methods, because callers would need different borrowing rights.
I compare the exact receiver before investigating the method body.
E0186 is the opposite direction of another common failure
When the trait declares an associated function but the impl adds &self, rustc reports the complementary mismatch, commonly E0185. Together they teach one rule: method-ness belongs to the contract.
This distinction is useful while searching. “Wrong number of arguments” may lead to E0050 discussions, but a diagnostic specifically mentioning a self declaration points to item-call identity.
I keep the exact compiler wording in evidence so related errors do not collapse into one vague page.
Object use depends on receiver design
Trait object dispatch is naturally connected to methods that can be called through a receiver. Associated functions without a receiver generally need Self: Sized or another design because a dyn Trait value does not reveal which concrete type should receive the static call.
Therefore changing a receiver while evolving a public trait can affect dyn compatibility and downstream APIs. I inspect trait-object users as well as concrete impls before calling it a small refactor.
The method-call Reference also explains the receiver search performed for method syntax.
Generated impls can lose the receiver token
Macro code may build a signature from separate name, arguments, and receiver fields. One branch can accidentally omit the receiver or print it as an ordinary typed parameter.
I test generated impls with actual calls, not compilation alone. A call like value.reset() proves the final item behaves as a method. For mutable receivers, the test also proves the binding and borrow are correctly mutable.
A wrapper cannot alter the trait slot
I can expose a convenience associated function in an inherent impl and let it construct or locate an instance before calling the trait method. That wrapper does not replace the trait implementation. The trait impl still needs the receiver declared by the contract.
This separation is often clean: the inherent function handles discovery or construction, while the trait method handles behaviour on an existing value. Giving both operations distinct names prevents users from wondering whether state is changed globally or on one instance.
My E0186 checklist
- Does the trait item declare
self,&self, or&mut self? - Does the implementation preserve that receiver?
- Is this operation truly about one instance?
- Did copied inherent code use an associated-function shape?
- Is the receiver's ownership and mutability also compatible?
- Could changing the trait affect dyn compatibility?
- Does a macro branch omit the receiver?
- Does the repaired evidence call the item with method syntax?
The core principle is that a receiver is part of the behavioural promise. A trait method acts through an instance, and its implementation cannot replace that relationship with a type-level function of the same name.