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