RFA-646 · Case file with fixtures · Case 618 of 694 · Compiler evidence
A Method Receiver Cannot Be an Arbitrary Method Generic
Method lookup needs a receiver structurally tied to Self. Use self, a borrow, Box, Rc, Arc, or Pin form, and move arbitrary wrappers into ordinary parameters or traits.
- 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
- Arbitrary wrapper polymorphism was placed in the special receiver slot which method lookup needs to relate structurally to Self.
- First discriminating check
- Choose a concrete ownership receiver such as self, a borrow, Box, Rc, Arc, or Pin, or use an ordinary wrapper parameter.
The first parameter named self has special method-call meaning. Rust must relate its concrete receiver form to the implementing Self type during method lookup. A method-specific generic can be almost anything satisfying its bounds, so it is not a valid receiver identity. The failing fixture uses generic R and receives E0801.
Receivers are not ordinary generic arguments
Common forms are self, &self, and &mut self. Supported smart-pointer forms include concrete shapes such as self: Box<Self>, Rc<Self>, Arc<Self>, and nested Pin forms under documented rules.
The official E0801 explanation says a generic type parameter defined on the method cannot be the receiver, even when bounded by Deref<Target = Self>. The Reference gives the method receiver grammar.
The compiler needs more than a promise that some R dereferences. Receiver adjustment and method availability must start from a recognised structural relationship.
Choose ownership in the receiver
The repaired fixture takes self: Rc<Self>, stating that calling start consumes one shared-ownership handle. The object may remain alive through other clones.
self consumes the value, &self observes shared state, and &mut self requires exclusive access. Box<Self> can consume a heap-owned receiver, while Arc<Self> often supports task or thread ownership when bounds allow it. The signature should match the operation’s lifecycle.
I avoid choosing Arc<Self> simply because async work is involved. A borrowed async method may be fine when its future does not outlive the caller. Spawning detached work usually needs owned state and an explicit shutdown owner.
Put arbitrary wrappers in a normal parameter
If an algorithm should accept any R: Deref<Target = Service>, I write an associated function or free function taking receiver: R, not a special self parameter. Callers use Service::start_with(wrapper) or another clear name.
Alternatively, a trait can be implemented for the wrapper type when the wrapper genuinely owns the behaviour. This keeps method resolution tied to its real concrete Self instead of pretending a generic wrapper is the service.
The standard library’s Receiver trait documentation reflects evolving arbitrary-receiver machinery. I pin toolchains and do not base stable public APIs on nightly examples without an explicit policy.
Smart-pointer receivers affect dyn compatibility
Trait methods callable through trait objects need receiver forms that can dispatch from the erased handle. Exotic receivers may prevent dyn use or require features. I test the actual Box<dyn Trait> or Arc<dyn Trait> call when dynamic dispatch is part of the design.
Receiver choice also influences object safety, Send/Sync requirements, and allocation. A method consuming Arc<Self> is not callable from &Self without obtaining shared ownership somehow. That friction can reveal unclear lifecycle design.
I keep constructors and runtime start methods separate. Construction returns an owned service; start may borrow for a scoped run or consume a handle into supervised background tasks.
Macros should preserve concrete receiver syntax
Code generators sometimes abstract every parameter into a generic list and accidentally transform a concrete receiver into R. Receiver tokens deserve their own structured representation.
Compile tests cover borrowed, mutable, owned, boxed, shared, pinned, trait-object, and invalid generic forms. Expansion tests confirm hygiene but compilation proves method-call syntax works.
Edition or feature changes can expand allowable receiver shapes, yet downstream MSRV remains the constraint. I use the simplest stable form carrying the needed ownership.
My E0801 checklist
- Is the self parameter type a method generic rather than a concrete Self-based form?
- Does the operation borrow, mutate, consume, or share ownership?
- Which standard receiver form states that lifecycle directly?
- Could an arbitrary wrapper become an ordinary parameter instead?
- Should behaviour belong to a trait implemented on the wrapper itself?
- Must the method remain callable through a trait object?
- Are Arc or Pin being introduced for a demonstrated requirement?
- Do generated code and MSRV tests cover the exact receiver syntax?
The core principle is that method syntax needs a concrete route from the receiver to Self. E0801 rejects a method-generic placeholder for that route. I state ownership with a supported receiver and model wrapper polymorphism elsewhere.