Advanced Generics and Type-Level Programming

Associated Type Bounds

Associated type bounds constrain the associated types of a trait directly inside another trait bound—using either equality constraints (Iterator<Item = u32>) or nested trait bounds (Iterator<Item: Display + Clone>, stabilized in Rust 1.79).

Equality Bounds vs. Trait Bounds on Associated Types

Constraint Kind Inline Associated Type Bound Syntax Equivalent where-Clause Projection
Exact type equality (=) T: Iterator<Item = String> T: Iterator, T::Item == String
Single trait bound (:) T: Iterator<Item: Display> T: Iterator, T::Item: Display
Multiple trait bounds (+) T: Iterator<Item: Display + Clone + 'static> T: Iterator, T::Item: Display + Clone + 'static
Nested associated bound T: IntoIterator<IntoIter: ExactSizeIterator> T: IntoIterator, T::IntoIter: ExactSizeIterator
GAT bound T: LendingIter<for<'a> Item<'a>: Debug> T: LendingIter, for<'a> T::Item<'a>: Debug

Using Inline Associated Type Bounds (Trait<Assoc: Bound>)

Inline associated type bounds eliminate the need to invent single-use generic type parameters or repeat long <T as Trait>::Assoc paths in where clauses—including inside impl Trait argument and return positions:

use std::fmt::{Debug, Display};

// Without `Item: Display`, this would require either a `where I::Item: Display`
// clause or a second generic type parameter `<I, T>`.
fn join_printable(iter: impl IntoIterator<Item: Display, IntoIter: ExactSizeIterator>) -> String {
    let iter = iter.into_iter();
    let total = iter.len();
    let mut out = format!("[{total} items]: ");
    for (idx, item) in iter.enumerate() {
        if idx > 0 {
            out.push_str(", ");
        }
        out.push_str(&item.to_string());
    }
    out
}

// Returning an iterator whose associated `Item` is constrained by a trait bound:
fn debuggable_numbers() -> impl Iterator<Item: Debug + Copy> {
    [10, 20, 30].into_iter()
}

fn main() {
    println!("{}", join_printable(vec!["alpha", "beta", "gamma"]));

    for n in debuggable_numbers() {
        println!("debuggable number: {:?}", n);
    }
}
[3 items]: alpha, beta, gamma
debuggable number: 10
debuggable number: 20
debuggable number: 30

Supertrait Bounds on Associated Types Inside Trait Definitions

A trait definition can attach trait bounds directly to its own associated type declaration (type Output: Clone + Send;). Every impl of that trait is then required to choose a type for Output that satisfies those bounds, and generic code using T: Processor can call .clone() on T::Output automatically without repeating where T::Output: Clone:

use std::fmt::Display;

trait SummarySource {
    // Imposes `Display` and `Clone` on every implementation's `Summary` type:
    type Summary: Display + Clone;

    fn summarize(&self) -> Self::Summary;
}

struct BuildReport {
    passed: usize,
}

impl SummarySource for BuildReport {
    type Summary = String;

    fn summarize(&self) -> Self::Summary {
        format!("{} tests passed", self.passed)
    }
}

fn print_duplicated<S: SummarySource>(source: &S) {
    // `s.clone()` and `{}` formatting work without extra `where S::Summary: ...` bounds!
    let s = source.summarize();
    let copy = s.clone();
    println!("{} | {}", s, copy);
}

fn main() {
    let report = BuildReport { passed: 42 };
    print_duplicated(&report);
}
42 tests passed | 42 tests passed