Mehdi Akiki
Rust Failure Atlas / Language and diagnostics

RFA-601 · Case file with fixtures · Case 573 of 694 · Compiler evidence

Nested Rust impl Trait Needs a Named Inner Type Parameter

Name the inner caller-selected type, then use it in the outer bound. This exposes equality relationships that nested anonymous syntax would hide.

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 inner caller-selected type participates in the outer generic relationship and therefore needs an identity that the signature can name.
First discriminating check
Declare the inner payload as a named generic parameter and use that same name in the outer trait's generic argument.

Argument-position impl Trait introduces an anonymous type parameter. Nesting another impl Trait inside its generic arguments would require an anonymous parameter whose relationship to the outer anonymous parameter has no supported declaration shape. The failing fixture does this and receives E0666.

Anonymous syntax is convenient at one boundary

fn accept(value: impl Display) says callers may supply any one concrete type implementing Display. The function body is still monomorphised for that type. This is concise when the parameter appears once and no other signature position needs to name it.

In impl Envelope<impl Payload>, the inner payload type matters to the outer bound. The official E0666 page requires making that inner type an explicit named generic parameter.

The restriction preserves a signature where type relationships can be stated, referenced, and checked without recursively hidden binders.

The repair names the payload type

The repaired fixture declares T: Payload and accepts impl Envelope<T>. Now T identifies the payload expected by the envelope.

If two parameters must use the same payload, the name becomes essential: both bounds can mention T. Two separate impl Payload occurrences would generally mean potentially different caller-chosen types, not automatic equality.

I choose semantic names such as Payload, Item, or Error in real APIs rather than a chain of letters once relationships grow. The compiler accepts short names, but reviewers need to see who chooses each type and where equality is required.

Named generics support additional bounds

A named type can appear in where clauses, return types, associated-type equality constraints, and lifetime bounds. For example, an envelope payload might need Serialize + Send + 'static, or two adapters might require the same error type.

The Reference describes argument-position impl Trait as anonymous type parameter. Moving to a named parameter is therefore not changing from dynamic to static dispatch. Both forms remain generic and monomorphised.

This corrects a common misconception: impl Trait in arguments is not a trait object. Dynamic dispatch would use something like &dyn Payload or Box<dyn Payload>, subject to dyn compatibility and ownership needs.

Who chooses the type guides the design

With a generic parameter, each caller chooses a concrete type satisfying the bounds. With an associated type, each trait implementation chooses one type. With a trait object, the concrete type is erased behind a runtime interface. These are different architectures.

If every Envelope implementation has one natural payload, an associated type may be simpler than Envelope<T>. If one envelope supports many payload types per call, the generic parameter is appropriate. I solve that design question rather than only spelling around E0666.

Return-position impl Trait is another model: the function chooses one hidden concrete type and callers know only its bounds. Nested return-position cases have their own current rules. I do not transfer intuitions between positions without reading the signature semantics.

Public syntax can affect compatibility

Changing a function from argument impl Trait to a named generic can affect how callers provide explicit generic arguments and is a public API consideration. The generics chapter helps explain monomorphisation, but release compatibility needs concrete compile tests.

I minimise exposed parameters while naming every relationship the API actually needs. Too many independent generics can make errors difficult; type aliases and helper traits can group real concepts without hiding ownership choices.

My E0666 checklist

  • Is one argument-position impl Trait nested inside another's generic arguments?
  • What concrete inner type relationship needs a reusable name?
  • Do multiple parameters require the same inner type or independent types?
  • Should the caller choose the inner type, or should an associated type belong to the implementor?
  • Is dynamic dispatch actually required, or are both forms static generics?
  • What additional lifetime, Send, serialization, or equality bounds apply?
  • Does changing public generic syntax affect explicit callers or MSRV?
  • Can semantic type names make compiler messages and review clearer?

The core principle is that anonymity is useful only while no one needs to refer to the type. Nested generic relationships do need that identity. E0666 asks me to name it, which usually makes the real API contract easier to understand.