Required Bounds on Generic Associated Types

Generic associated types often need explicit where Self: 'a bounds because the trait methods prove those bounds at use sites. Writing the required bounds on the GAT preserves the implementer’s freedom to return borrowed associated types.

What it is

A Generic Associated Types declaration can have its own lifetime, type, and const parameters. When a trait method returns Self::Item<'a> from &'a self or &'a mut self, the compiler can prove Self: 'a inside that method signature. Rust currently requires such provable bounds to be repeated on the GAT declaration. The familiar shape is type Item<'a> where Self: 'a;. This rule looks redundant at first, but it allows implementers to define associated types that borrow from Self. Without the bound on the GAT itself, some valid impl definitions would be rejected. The Reference notes that these rules may be loosened in the future, so treat them as a current language requirement. The rule is central to lending iterators, streaming parsers, and view APIs.

How it works

Required GAT bounds are derived from how the GAT appears in trait methods. If a method has fn next<'a>(&'a mut self) -> Self::Item<'a>, then the receiver proves Self: 'a. The GAT declaration must include where Self: 'a. When several methods mention the same GAT, Rust uses the intersection of bounds that are true at all relevant uses, not the union. Bounds can propagate through other associated type bounds. For example, type Iter<'a>: Iterator<Item = Self::Item<'a>> where Self: 'a; carries the same relationship. In impls, the associated type definition repeats the matching where clause. This repetition is not aesthetic; it is part of the trait contract.

Example

trait LendingIterator {
    type Item<'a>
    where
        Self: 'a;
 
    fn next<'a>(&'a mut self) -> Option<Self::Item<'a>>;
}
 
struct Windows<'data> {
    data: &'data [u8],
    pos: usize,
}
 
impl<'data> LendingIterator for Windows<'data> {
    type Item<'a> = &'a [u8]
    where
        Self: 'a;
 
    fn next<'a>(&'a mut self) -> Option<Self::Item<'a>> {
        let end = self.pos.checked_add(2)?;
        let window = self.data.get(self.pos..end)?;
        self.pos += 1;
        Some(window)
    }
}
 
fn main() {
    let mut windows = Windows { data: b"rust", pos: 0 };
    assert_eq!(windows.next(), Some(&b"ru"[..]));
    assert_eq!(windows.next(), Some(&b"us"[..]));
}

Edge cases

trait View {
    type Ref<'a>: Copy
    where
        Self: 'a;
 
    fn view<'a>(&'a self) -> Self::Ref<'a>;
}
 
impl View for u32 {
    type Ref<'a> = &'a u32 where Self: 'a;
 
    fn view<'a>(&'a self) -> Self::Ref<'a> {
        self
    }
}
 
fn main() {
    let n = 5_u32;
    assert_eq!(*n.view(), 5);
}

Best practice

  • ✅ Start lending-style GATs with type Item<'a> where Self: 'a;.
  • ✅ Repeat the where clause in the impl’s associated type definition.
  • ✅ Use descriptive associated type names such as Item, Ref, Guard, View, or Iter.
  • ✅ Keep method signatures close to the GAT declaration so readers can see why the bound is required.
  • ✅ Prefer GATs over unsafe lifetime extension when returned values borrow from self.
  • ✅ Link GAT-heavy APIs to Lifetimes and Higher-Ranked Trait Bounds for readers who need the model.

Pitfalls

  • ⚠️ Removing where Self: 'a because it “looks unused”; the trait may stop compiling or become less implementable.
  • ⚠️ Returning borrowed values through an ordinary associated type when a GAT is needed.
  • ⚠️ Adding unnecessary bounds to the GAT that are only true for one method but not all uses.
  • ⚠️ Expecting GATs to make every trait dyn-compatible; many such traits are not object safe.
  • ⚠️ Confusing a GAT lifetime parameter with a lifetime chosen by the implementer; callers instantiate it at use sites.

See also

Advanced Type System Generic Associated Types Associated Types Trait Bounds Where Clauses Lifetimes Higher-Ranked Trait Bounds Lending Iterators with GATs Return-Position impl Trait in Traits dyn Compatibility (Object Safety)

Sources