Associated Types and Advanced Traits

Associated Types

An associated type is a type placeholder declared inside a trait block with the type keyword that each implementing type specifies as a concrete type in its impl block.

Declaring and Implementing an Associated Type

Inside a trait definition, declare an associated type with type Name; (optionally with trait bounds such as type Name: Debug;). Inside the impl block, bind it to a concrete type using type Name = ConcreteType;:

use std::fmt::Debug;

trait Container {
    type Item: Debug;

    fn insert(&mut self, item: Self::Item);
    fn first(&self) -> Option<&Self::Item>;
}

struct IntStack {
    items: Vec<i32>,
}

impl Container for IntStack {
    type Item = i32;

    fn insert(&mut self, item: Self::Item) {
        self.items.push(item);
    }

    fn first(&self) -> Option<&Self::Item> {
        self.items.first()
    }
}

fn main() {
    let mut stack = IntStack { items: Vec::new() };
    stack.insert(42);
    println!("First item: {:?}", stack.first());
}
First item: Some(42)

Referencing Associated Types in Generic Bounds

In generic functions, you can refer to an associated type via C::Item or constrain it to a specific concrete type inside the trait bound using equality syntax (C: Container<Item = i32>):

trait Parser {
    type Output;
    fn parse(&self, input: &str) -> Option<Self::Output>;
}

struct U32Parser;

impl Parser for U32Parser {
    type Output = u32;

    fn parse(&self, input: &str) -> Option<Self::Output> {
        input.trim().parse().ok()
    }
}

// Constrains P so its associated Output type must be u32:
fn parse_and_double<P: Parser<Output = u32>>(parser: &P, raw: &str) -> Option<u32> {
    parser.parse(raw).map(|n| n * 2)
}

fn main() {
    let parser = U32Parser;
    println!("Result: {:?}", parse_and_double(&parser, " 21 "));
}
Result: Some(42)

Associated Types with Bounds

When a trait declares a bound on an associated type (such as type Error: std::fmt::Display;), every implementation must choose a concrete type that satisfies that bound, and generic code using the trait can rely on that bound automatically without repeating it in where clauses:

use std::fmt::Display;

trait JobRunner {
    type Error: Display;

    fn run(&self) -> Result<(), Self::Error>;
}

fn execute_and_log<R: JobRunner>(runner: &R) {
    if let Err(err) = runner.run() {
        // `err` can be formatted with `{}` because `R::Error: Display` is guaranteed by the trait
        println!("Job failed: {err}");
    }
}

struct FailingJob;

impl JobRunner for FailingJob {
    type Error = &'static str;

    fn run(&self) -> Result<(), Self::Error> {
        Err("connection timed out")
    }
}

fn main() {
    execute_and_log(&FailingJob);
}
Job failed: connection timed out