Mehdi Akiki
Rust Failure Atlas / Language and diagnostics

RFA-122 · Case file with fixtures · Case 94 of 694 · Compiler evidence

Why a Generic Associated Type Needs where Self: 'a

A GAT that may borrow from an implementing value must state that Self lives for the borrow lifetime. The required bound preserves implementation flexibility and makes the lending relationship explicit.

Reviewed
Rust
Rust 1.98.1
Targets
all targets
Profiles
check, dev, release, test

Direct answer

What this Rust failure means

Why it happens
The associated type may borrow from its implementing value, so forming Item<'a> requires the implementing Self type to remain valid for 'a.
First discriminating check
Find methods projecting the associated type from a self borrow and add the required outlives condition on the GAT declaration.

The failing program declares a lending iterator trait:

trait LendingIterator {
    type Item<'a>;
    fn next<'a>(&'a mut self) -> Option<Self::Item<'a>>;
}

Rust 1.98.1 says Item is missing a required bound and suggests where Self: 'a. Adding that clause makes the repaired declaration compile.

The bound is not saying every implementation is 'static. It relates one associated-type instantiation to the lifetime for which its implementing value is available.

A GAT is a family of types

An ordinary associated type chooses one type per trait implementation. A generic associated type can choose a type for each lifetime or type parameter. The associated types reference describes these declarations and their bounds.

For a lending iterator, an implementation might use:

type Item<'a> = &'a mut Record where Self: 'a;

Each call borrows the iterator and receives an item tied to that borrow. The returned type can change with 'a; it cannot outlive the object from which it borrows.

I picture Item not as one box but as a function at the type level:

Item<'short> -> type valid for a short borrow
Item<'long>  -> type valid for a long borrow

Self: 'a permits borrowing from Self

The lifetime bounds reference explains outlives relationships. Self: 'a requires the implementing type to be valid for 'a when Item<'a> is formed.

This is necessary for implementations whose associated item contains references derived from self. Without the relation, the trait declaration could demand an associated type at a lifetime where Self itself is not valid, preventing useful implementations or making their soundness impossible to express.

The compiler says the bound preserves maximum flexibility. It derives required bounds from how the associated type is used in trait methods, rather than treating the clause as optional documentation.

Put the bound on the associated type declaration

The relevant form is:

type Item<'a>
where
    Self: 'a;

Implementations repeat the required bound as appropriate. Putting a similar clause only on next does not necessarily define when every projection Self::Item<'a> is valid. The associated type itself needs its well-formedness condition.

I follow the compiler suggestion exactly first, then inspect whether the trait API truly needs lending. Adding unrelated 'static bounds can make the error disappear by making the API much less useful.

Lending changes iteration ergonomics

The standard Iterator trait has one Item type independent of each next borrow. This means an iterator generally cannot yield references into its own mutable internal buffer with the lifetime of each call.

A lending iterator can express that pattern with a GAT, but callers cannot necessarily hold one yielded item and call next again because the first item may retain the mutable borrow. This is the safety property, not an inconvenience to erase.

I use this design for parsed views, reusable buffers, or database cursors only when the borrowed result provides real value. Owned items remain simpler for many APIs.

Trait bounds propagate to consumers

Functions generic over a lending trait often need bounds on for<'a> L::Item<'a>. Higher-ranked bounds, auto traits, and current trait-solver limitations can produce complex messages far from the trait declaration.

I build the API in layers: first one concrete implementation, then one generic consumer, then additional trait bounds. Compile-fail fixtures are useful because a small change in the declaration can widen or narrow all projections.

The Self: 'a clause does not alone guarantee covariance, Send, or any particular reference type. It only establishes the required outlives relationship.

My debugging sequence

When a GAT reports a missing required bound, I do this:

  1. Find every trait method that forms the associated type at a borrowed lifetime.
  2. Ask whether implementations may borrow from Self.
  3. Add the suggested where Self: 'a on the associated type declaration.
  4. Implement one concrete lending type and test how long returned items can live.
  5. Avoid broad 'static bounds that destroy lending behavior.
  6. Add generic consumers incrementally and preserve diagnostic fixtures.

The bound states the fact the API already relies on: a value cannot lend data for a lifetime in which the lender does not exist. Writing that relationship on the GAT gives implementations room to express borrowed associated types without promising impossible lifetimes.