August 3, 2021·4 min read·engineeringrusttraitslifetimes

Rust asks for testing of generic associated types

GATs lose their incomplete warning on nightly, with stabilization still ahead. Lending iterators show why an associated type sometimes needs the lifetime of each method call.

An iterator over overlapping mutable windows needs each returned window to stop being borrowed before the next one can be produced. The standard Iterator trait cannot express that relationship through one fixed associated Item type. Generic associated types give the item its own lifetime parameter, allowing the result to borrow for the duration selected by a particular call.

The August 3 update, by Jack Huey for the Traits Working Group, reports that the nightly generic_associated_types feature is no longer considered incomplete. This removes a warning. Stabilization remains a goal, and the team asks users to find bugs, improve diagnostics, and report useful patterns before that decision.

I find this feature interesting because it expands what a library can express about borrowing. The benefit is not an abstract increase in type-system power alone. A trait can describe a relationship between a method's borrow and the value it returns, giving the compiler information needed to reject overlapping mutable access.

An associated type can depend on a call's lifetime

The motivating design appears in RFC 1598, which began in April 2016. It proposes associated types that take parameters. A trait can then provide a family of related result types instead of selecting one unparameterized associated type for every use.

The August post's LendingIterator example makes the relationship visible. This is nightly feature syntax as presented in the announcement, not stable Rust available on this date.

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

The next method borrows self for a lifetime called a. The returned associated item receives that same lifetime argument. An implementation can choose a mutable reference with that lifetime as its Item. The relationship follows each invocation's borrow instead of requiring the result to borrow for the full lifetime of some underlying storage.

That is useful when the next call needs mutable access to storage used by the previous item. The caller cannot retain that mutable borrow and simultaneously request another item through the same iterator borrow. The trait's type relationship expresses the sequencing requirement instead of leaving it to a comment or unsafe caller convention.

The where clause on Item requires Self to outlive the lifetime used for that item. The announcement explains why an implementation returning a borrowed slice needs the condition. It also identifies this clause as a point of confusion and asks for feedback about possible alternatives. In that nightly implementation, its exact ergonomic treatment is part of the work being tested.

Overlapping windows expose the constraint

The announcement uses a hypothetical windows_mut operation on a mutable slice. Each window advances its starting position by one. When the window size is greater than one, successive windows overlap in the elements they cover.

An ordinary Iterator implementation giving every returned mutable slice the lifetime of the original storage would permit a caller to retain one result while asking for another. That could produce overlapping mutable references. The failed implementation in the post receives a borrow of self for one duration but tries to return a value required to remain valid for the longer storage duration.

A lending iterator instead returns a slice tied to the current borrow of self. The next window can overlap the previous window's region because the previous borrow must no longer be active when another mutable call occurs. The data can overlap across successive operations without allowing incompatible borrows to coexist.

This does not turn the standard Iterator trait into a lending trait. It gives a library author the ability to declare another trait with the required relationship. Generic consumers then need to use that contract. The capability is about expressing the appropriate API, not silently changing every existing iterator's semantics.

I like this example because the compiler restriction corresponds to a concrete access rule. The library wants sequential mutation of overlapping regions. The type signature describes why those mutations are valid in sequence and why keeping two conflicting windows alive would be invalid.

Lifetimes are not the only possible parameters. The August post describes associated types taking type or constant parameters too. Its pointer-family example associates Pointer of T with a trait and gives that resulting type a Deref bound targeting T.

An implementation can select Arc of T, while another selects Rc of T. A structure parameterized over the family can then name the family's pointer to String without choosing its concrete pointer representation directly. The associated type's argument varies the pointed-to type while the implementing family controls the pointer choice.

The RFC explains that this is a restricted form of abstraction rather than every feature grouped under higher-kinded polymorphism. Its goal is to let traits associate parameterized types. It does not implement every way another language might accept a type constructor as a generic argument. Keeping the scope explicit makes the pointer example understandable without promising unrelated abstractions.

Compiler work and remaining limits

The announcement lists years of implementation work beyond parsing new syntax. Lifetime resolution, validation of projected types, and changes to trait solving all participate. For a time, supporting GATs appeared to require moving to the experimental Chalk solver. The post explains that the existing compiler has instead received substantial adaptations.

Traits containing GATs are not object safe in the announced implementation. A user cannot assume that a generic lending trait can become a corresponding dyn trait object. The team describes further design and implementation work for that capability and considers it suitable for a later compatible extension.

Some diagnostic issues also remain. The request for testing therefore includes confusing errors as well as outright compiler bugs. A feature can support useful declarations while still needing evidence about how clearly users can diagnose rejected ones.

The present change is a more mature nightly implementation ready for wider experiments. The overlapping-window example supplies a concrete reason to test it. A result type can now depend on the borrow made by each call, which is the relationship a lending API needs to preserve while controlling mutable access.