Advanced Generics and Type-Level Programming

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.