Generic Associated Types (GATs)
A Generic Associated Type (GAT) is an associated type inside a trait declaration that carries its own generic lifetime, type, or const parameters—such as type Item<'a> where Self: 'a; or type Container<T>;. Stabilized in Rust 1.65, GATs allow a trait's output type to depend on a call-site borrow lifetime 'a or an arbitrary element type T chosen per method invocation.
Problem Solved #1: Lending Iterators (type Item<'a>)
In the standard Iterator trait (type Item; fn next(&mut self) -> Option<Self::Item>), Self::Item has no lifetime parameter tied to &mut self. As a result, a standard Iterator cannot return a reference borrowing from self's own internal buffer (for example, yielding overlapping sliding windows &mut [T] or reusing a single scratch String buffer on every .next() call).
A LendingIterator trait uses a lifetime GAT type Item<'a> where Self: 'a; so each call fn next<'a>(&'a mut self) -> Option<Self::Item<'a>> ties the yielded item's lifetime directly to the duration of that single &'a mut self borrow:
Standard Iterator (`type Item` — independent of `&mut self`):
iter.next() -> Item1 \
iter.next() -> Item2 +--> Item1 and Item2 can coexist simultaneously!
(Cannot borrow from a mutated buffer inside `iter`)
LendingIterator (`type Item<'a>` — tied to `&'a mut self`):
iter.next() -> Item<'a1> (borrows `iter` for `'a1`)
// Item<'a1> must be dropped before calling `iter.next()` again for `'a2`!
trait LendingIterator {
type Item<'a>
where
Self: 'a;
fn next(&mut self) -> Option<Self::Item<'_>>;
}
struct WindowsMut<'slice, T> {
slice: &'slice mut [T],
window_size: usize,
pos: usize,
}
impl<'slice, T> LendingIterator for WindowsMut<'slice, T> {
type Item<'a>
= &'a mut [T]
where
Self: 'a;
fn next(&mut self) -> Option<Self::Item<'_>> {
if self.pos + self.window_size > self.slice.len() {
return None;
}
let start = self.pos;
self.pos += 1;
Some(&mut self.slice[start..start + self.window_size])
}
}
fn main() {
let mut data = [1, 2, 3, 4];
let mut windows = WindowsMut {
slice: &mut data,
window_size: 2,
pos: 0,
};
while let Some(win) = windows.next() {
win[0] *= 10;
println!("mutated window: {:?}", win);
}
println!("final array: {:?}", data);
}
mutated window: [10, 2]
mutated window: [20, 3]
mutated window: [30, 4]
final array: [10, 20, 30, 4]
Problem Solved #2: Type-Constructor Families (type Pointer<T>)
A type GAT (type Pointer<T>) allows abstracting over a family of smart pointer or container constructors (such as switching an entire data structure between single-threaded Rc<T> and multi-threaded Arc<T> via a single trait parameter):
use std::ops::Deref;
use std::rc::Rc;
use std::sync::Arc;
trait PointerFamily {
type Pointer<T>: Deref<Target = T> + Clone;
fn new<T>(value: T) -> Self::Pointer<T>;
}
struct RcFamily;
impl PointerFamily for RcFamily {
type Pointer<T> = Rc<T>;
fn new<T>(value: T) -> Self::Pointer<T> {
Rc::new(value)
}
}
struct ArcFamily;
impl PointerFamily for ArcFamily {
type Pointer<T> = Arc<T>;
fn new<T>(value: T) -> Self::Pointer<T> {
Arc::new(value)
}
}
struct SharedConfig<F: PointerFamily> {
app_name: F::Pointer<String>,
}
impl<F: PointerFamily> SharedConfig<F> {
fn new(name: &str) -> Self {
Self {
app_name: F::new(name.to_string()),
}
}
}
fn main() {
let single_threaded = SharedConfig::<RcFamily>::new("local-cli");
let multi_threaded = SharedConfig::<ArcFamily>::new("http-server");
println!("Rc config: {}", *single_threaded.app_name);
println!("Arc config: {}", *multi_threaded.app_name);
}
Rc config: local-cli
Arc config: http-server
Object Safety Restriction on GATs
Traits that declare a Generic Associated Type with type or lifetime parameters are not dyn-compatible (cannot be used as dyn Trait) unless every generic parameter of the GAT is specified, because a single vtable cannot represent an unbounded family of return layouts.