Mehdi Akiki
Rust Failure Atlas / Language and diagnostics

RFA-614 · Case file with fixtures · Case 586 of 694 · Compiler evidence

A Rust Trait Object Associated Type Can Be Bound Only Once

A trait object needs one concrete value for each required associated type. Remove duplicates and preserve one coherent erased interface.

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
Merged or generated object bounds created two claims for one implementation-selected associated type instead of one coherent erased interface.
First discriminating check
Trace the duplicate bound sources and retain exactly one authoritative value for each associated type in the trait object.

A trait object's associated types must be known wherever the erased interface uses them. Each associated type gets one binding. The failing fixture writes Item = u32 twice for Iterator, so rustc emits E0719.

Repetition does not strengthen an equality

Both bindings happen to request u32, but a type contract should still have one source of truth. If duplicates were allowed and later diverged, the object would require Item to be two incompatible types at once.

The official E0719 explanation fixes the error by removing the repeated specifier. This is a structural diagnostic rather than a trait-solver question about whether u32 equals itself.

Duplicate entries commonly appear after merging bounds, macro expansion, or copying a long trait-object alias.

The repair defines one erased iterator interface

The repaired fixture uses dyn Iterator<Item = u32> and accepts it behind &mut. The drain function sums the values from a range through that erased interface.

The reference is essential because a bare trait object is dynamically sized. &mut dyn Iterator also permits calling next, which mutates iterator state. A Box could provide ownership when the iterator must be stored.

The Reference documents trait objects, including dynamic dispatch and object lifetime bounds.

Associated types differ from generic parameters

An associated type is selected by the trait implementation. For one implementation of Iterator, there is one Item. This lets callers write bounds without making the item an extra trait parameter at every use.

Generic parameters are chosen according to the generic API and can support multiple implementations for different arguments where coherence permits. The “who chooses?” distinction explains why object types need associated-type constraints.

The Book section on associated types provides the foundational model.

Several associated types need distinct names

More complex traits may define Item, Error, Context, or other types. Each can be bound once: dyn Service<Response = R, Error = E>. A macro that assembles bounds should deduplicate by associated-item identity, not merely by token text.

Supertraits can expose associated types with the same short name under different trait identities. Fully qualified syntax may be needed in complex generic contexts. I reduce aliases and identify the owning trait when diagnostics are confusing.

If two interfaces truly require different item types, one trait object cannot pretend one associated type serves both. Separate objects, an enum, adapters, or a redesigned trait may represent the heterogeneity.

Dynamic dispatch has ownership and performance tradeoffs

Trait objects enable collections and APIs containing different concrete iterator types. They add pointer indirection, restrict inlining across the dynamic call, and require dyn-compatible methods. Generic functions may be faster or easier when heterogeneity is not needed.

I choose dynamic dispatch for architecture or code-size reasons and measure hot paths. E0719 itself is not an argument for or against erasure; it only requires a coherent erased contract.

Public aliases help keep complex bounds consistent. I define the associated types once, document ownership and lifetime, and test with multiple concrete implementors.

Why this appears in generated APIs

I often meet the duplicate far away from the final type. One macro contributes a default Item, another adapter adds an explicit Item, and their token streams join in a public alias. The final diagnostic is simple, but deleting either fragment without tracing provenance can silently choose the wrong contract. I expand the macro or inspect generated documentation, decide which layer owns the associated type, and make the other layer accept that decision as an input.

This matters in plugin registries and iterator pipelines because erasure is normally introduced at an architectural boundary. The boundary should say one exact thing about values crossing it. When two components both try to specify the same associated type, I treat E0719 as evidence of duplicated ownership in the API design, not only duplicated syntax.

My E0719 checklist

  • Which associated item appears more than once in the trait object?
  • Did source code, a macro, or merged type alias introduce the duplicate?
  • Is one binding the authoritative intended type?
  • Does the trait object sit behind an appropriate reference or owning pointer?
  • Which trait implementation chooses each associated type?
  • Are similarly named items owned by different supertraits?
  • Does the design actually need heterogeneous dynamic dispatch?
  • Would one public alias prevent bound lists from drifting?

The core principle is that erasure still requires one coherent callable interface. Each associated type is part of that interface and receives exactly one value. E0719 removes duplicate claims before they can diverge or obscure the object contract.