Trait Bounds / where Clause
Trait bounds in Rust require that a generic type parameter implements a specific trait. Use the T: Trait syntax to specify them, and use a where clause to group multiple bounds for better readability.
Syntax
use std::fmt::{Display, Debug};
// Basic trait bound syntax.
fn print_item<T: Display>(item: T) {
println!("{}", item);
}
// Multiple trait bounds (combined with +).
fn print_debug_and_display<T: Debug + Display>(item: T) {
println!("Display: {}", item);
println!("Debug: {:?}", item);
}
// Using a where clause for trait bounds (cleaner when there are many type parameters).
fn compare_and_print<T, U>(t: T, u: U) -> String
where
T: Display + PartialOrd,
U: Display + Debug,
{
format!("t={}, u={:?}", t, u)
}
// impl Trait syntax (shorthand form).
fn print_it(item: &impl Display) {
println!("{}", item);
}
Syntax Reference
| Syntax | Description |
|---|---|
| fn foo<T: Trait>(x: T) | Requires type T to implement Trait. |
| fn foo<T: Trait1 + Trait2>(x: T) | Requires type T to implement multiple traits. |
| fn foo(x: &impl Trait) | impl Trait syntax, usable for function parameters and return types. |
| fn foo() -> impl Trait | Returns some type that implements Trait (the concrete type is hidden). |
| where T: Trait1, U: Trait2 | Groups trait bounds in a where clause for improved readability. |
| T: 'static | Requires type T to satisfy the 'static lifetime (contains no references). |
| T: Clone + Send + Sync | Example trait bound for types that can be safely shared across threads. |
Sample Code
use std::fmt::Display;
// Returns the largest value in a slice (requires PartialOrd).
fn largest<T: PartialOrd>(list: &[T]) -> &T {
let mut largest = &list[0];
for item in list.iter() {
if item > largest {
largest = item;
}
}
largest
}
// Example with multiple trait bounds.
fn describe<T: Display + std::fmt::Debug + Clone>(item: T) {
let cloned = item.clone();
println!("Display: {}", item);
println!("Debug: {:?}", cloned);
}
// Example using a where clause with multiple type parameters.
fn pair_display<T, U>(t: &T, u: &U)
where
T: Display,
U: Display,
{
println!("({}, {})", t, u);
}
// Using impl Trait to hide the return type (typical closure example).
fn make_adder(x: i32) -> impl Fn(i32) -> i32 {
move |n| n + x
}
// Example of adding a trait bound to a struct.
struct Wrapper<T: Display> {
value: T,
}
impl<T: Display> Wrapper<T> {
fn new(value: T) -> Self {
Wrapper { value }
}
fn show(&self) {
println!("Wrapper({})", self.value);
}
}
fn main() {
// Using the largest function.
let numbers = vec![34, 50, 25, 100, 65];
println!("Largest number: {}", largest(&numbers));
let chars = vec!['y', 'm', 'a', 'q'];
println!("Largest char: {}", largest(&chars));
// Using the describe function.
describe(42);
describe("hello");
// Using pair_display.
pair_display(&3.14, &"world");
// Returning a closure with make_adder.
let add5 = make_adder(5);
let add10 = make_adder(10);
println!("add5(3) = {}", add5(3)); // 8
println!("add10(3) = {}", add10(3)); // 13
// Using Wrapper.
let w = Wrapper::new(100);
w.show();
let ws = Wrapper::new("hello");
ws.show();
}
Notes
Trait bounds and impl Trait serve different purposes. Use impl Trait when a single type is sufficient and you want concise syntax. When multiple parameters must be the same type, use the explicit trait bound syntax (T: Trait).
Using impl Trait as a return type allows each function to return a different concrete type. However, you cannot return different types from different branches — use Box<dyn Trait> in that case.
For defining and implementing traits, see trait / impl Trait for Type.
If you find any errors or copyright issues, please contact us.