Type System Fundamentals

Associated Types

In Rust's type system, an associated type (type Output;) is a type-level function attached to a trait implementation that maps a concrete implementing type Self to a uniquely determined companion type Self::Output.

Associated Types as Type-Level Projections (<T as Trait>::Assoc)

When a type T implements a trait Trait { type Output; }, the type system treats <T as Trait>::Output (or T::Output when unambiguous) as a type projection—computing a concrete type from T:

Input Type (`Self`)          Trait Implementation          Projected Type (`Self::Item`)
--------------------         --------------------          -----------------------------
std::vec::IntoIter<String> ──(impl Iterator)────────────>  String
std::slice::Iter<'a, u32>  ──(impl Iterator)────────────>  &'a u32
std::str::Chars<'a>        ──(impl Iterator)────────────>  char

Constraining Associated Types in Generic Signatures

A generic function can constrain an associated type in three ways:

  1. Equality constraint (I: Iterator<Item = u32>): Fixes the associated type to a specific concrete type.
  2. Trait bound on the projected type (where I: Iterator, I::Item: Debug): Allows any Item type as long as it implements Debug.
  3. Associated type bound syntax (I: Iterator<Item: Debug>): Concise inline syntax (stabilized in Rust 1.79) equivalent to where I: Iterator, I::Item: Debug!
use std::fmt::Debug;

// Inline associated type bound `I: IntoIterator<Item: Debug>`:
fn print_all<I>(iterable: I)
where
    I: IntoIterator<Item: Debug>,
{
    for item in iterable {
        print!("{item:?} ");
    }
    println!();
}

fn main() {
    print_all([10, 20, 30]);
    print_all(["rust", "2024"]);
}
10 20 30 
"rust" "2024" 

Generic Associated Types (GATs)

An associated type inside a trait can also declare its own generic lifetime or type parameters (type Item<'a> where Self: 'a;), known as a Generic Associated Type (GAT). GATs allow a trait to project a type that borrows from &'a self (such as a streaming iterator or zero-copy cursor whose yielded item borrows internal buffer memory) or construct a family of containers (type Container<T>;):

trait WindowSource {
    // Generic Associated Type parameterized over borrow lifetime `'a`:
    type Window<'a>
    where
        Self: 'a;

    fn get_window(&self, start: usize, len: usize) -> Option<Self::Window<'_>>;
}

impl<T> WindowSource for Vec<T> {
    type Window<'a>
        = &'a [T]
    where
        T: 'a;

    fn get_window(&self, start: usize, len: usize) -> Option<Self::Window<'_>> {
        self.get(start..start + len)
    }
}

fn main() {
    let data = vec![10, 20, 30, 40, 50];
    println!("Window 1..4: {:?}", data.get_window(1, 3));
}
Window 1..4: Some([20, 30, 40])