Combining Generics and Trait Bounds
A trait bound specifies, using a trait, the constraints that a generic type parameter must satisfy. This guarantees at compile time that a type has certain methods or capabilities.
Syntax
// Basic trait bound (trait name after :)
fn function_name<T: TraitName>(arg: T) { ... }
// Requiring multiple traits (combined with +)
fn function_name<T: Clone + Debug>(arg: T) { ... }
// where clause syntax (cleaner for complex bounds)
fn function_name<T, U>(a: T, b: U)
where
T: Clone + Debug,
U: Display + PartialOrd,
{ ... }
// impl Trait syntax (shorthand for arguments and return types)
fn function_name(arg: impl TraitName) -> impl TraitName { ... }
Syntax Reference
| Syntax | Description |
|---|---|
| <T: Trait> | Specifies a trait bound on the type parameter T. |
| <T: A + B> | Requires the type to implement multiple traits at once. |
| where T: Trait | Writes the bound on a separate line using a where clause. Makes complex conditions easier to read. |
| impl Trait | Shorthand syntax usable as an argument or return type. Equivalent to an anonymous type parameter. |
| dyn Trait | Dynamic dispatch (trait object). Resolves methods at runtime. |
Sample Code
use std::fmt::{Debug, Display};
// Multiple trait bounds: requires both Clone and Debug.
fn print_twice<T: Clone + Debug>(item: T) {
let copy = item.clone();
println!("{:?} / {:?}", item, copy);
}
// where clause: improves readability when there are two or more parameters.
fn compare_and_show<T, U>(a: T, b: U) -> String
where
T: Display + PartialOrd,
U: Display,
{
format!("a={}, b={}", a, b)
}
// impl Trait syntax: returns a type that implements the trait.
fn make_adder(x: i32) -> impl Fn(i32) -> i32 {
move |y| x + y
}
fn main() {
print_twice(vec![1, 2, 3]); // [1, 2, 3] / [1, 2, 3]
let result = compare_and_show(42, "hello");
println!("{}", result); // a=42, b=hello
let add5 = make_adder(5);
println!("{}", add5(10)); // 15
}
Overview
Trait bounds let you guarantee at compile time that a generic type parameter supports certain methods. If you pass a type that does not satisfy the bounds, the compiler produces an error, preventing runtime failures.
When the number of bounds grows, function signatures can become hard to read. In that case, it is common practice to use a where clause to organize the bounds on separate lines.
When using impl Trait as a return type, the returned type must be a single, fixed type. If you need to return different types that implement a trait depending on a condition, use Box<dyn Trait> (dynamic dispatch) instead.
If you find any errors or copyright issues, please contact us.