Trait Bounds
Trait bounds (T: Trait) constrain generic type parameters in Rust's type system, specifying the exact capabilities and lifetime requirements a type must satisfy so the compiler can type-check a generic body once—before monomorphization ever occurs.
Definition-Site Type Checking (Why Rust Generics Are Not C++ Templates)
In C++ templates, an unconstrained template template<typename T> T add(T a, T b) { return a + b; } is only type-checked when instantiated with a concrete type, producing deep instantiation tracebacks when a type lacks +.
In Rust, generic functions are type-checked at their definition site against their declared trait bounds:
- Inside
fn process<T>(x: T), you cannot call any method, operator, or clone onxunless that capability is declared inT's trait bounds (T: Clone,T: Add,T: Display). - Conversely, once
fn process<T: Bound>(x: T)compiles, the compiler guarantees that every caller typeTsatisfyingBoundwill compile insideprocesswithout internal type errors.
Four Forms of Trait and Lifetime Bounds
| Bound Form | Syntax | Meaning |
|---|---|---|
| Single / Multiple trait bounds | T: Clone + Debug |
T must implement both Clone and Debug |
Argument-position impl Trait |
fn f(x: impl Display + Clone) |
Shorthand for an anonymous generic type parameter |
| Lifetime outlives bound | T: 'a or T: 'static |
Any references contained inside T must live at least as long as 'a (or 'static) |
| Higher-Ranked Trait Bound (HRTB) | F: for<'a> Fn(&'a str) -> bool |
F satisfies the bound for every possible lifetime 'a |
use std::fmt::{Debug, Display};
fn format_and_clone<T>(item: &T) -> (String, T)
where
T: Display + Debug + Clone,
{
let formatted = format!("display={item}, debug={item:?}");
(formatted, item.clone())
}
fn main() {
let (summary, copy) = format_and_clone(&42_u32);
println!("{summary} | cloned={copy}");
}
display=42, debug=42 | cloned=42
Conditional Method Availability via Bounded impl Blocks
Placing different trait bounds on separate impl<T: ...> Wrapper<T> blocks enables type-conditional APIs where methods exist on Wrapper<T> if and only if T satisfies the corresponding bound:
#[derive(Debug)]
struct BoxedValue<T>(T);
impl<T: Copy> BoxedValue<T> {
fn duplicate(&self) -> (T, T) {
(self.0, self.0)
}
}
impl<T: std::ops::Add<Output = T> + Copy> BoxedValue<T> {
fn doubled(&self) -> T {
self.0 + self.0
}
}
fn main() {
let num = BoxedValue(21);
let label = BoxedValue("ok"); // `&str` is `Copy`, but does NOT implement `Add`
println!("num duplicate: {:?}, doubled: {}", num.duplicate(), num.doubled());
println!("label duplicate: {:?}", label.duplicate());
// `label.doubled()` does not exist because `&str: !Add`
}
num duplicate: (21, 21), doubled: 42
label duplicate: ("ok", "ok")