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