Mehdi Akiki
Rust Failure Atlas / Language and diagnostics

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.