Trait Bounds

Using trait bounds to constrain generic types.

Advanced⏱️ 45 min📚 Prerequisites: 1

Trait Bounds

Trait bounds allow us to constrain generic types so that only types implementing certain traits are allowed.

Basic Trait Bound

RUST
fn largest<T: PartialOrd>(list: &[T]) -> Option<&T> {
    // ...
}

Multiple Trait Bounds

RUST
fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {
    // ...
}

where Clause

RUST
fn some_function<T, U>(t: &T, u: &U) -> i32
where
    T: Display + Clone,
    U: Clone + Debug,
{
    // ...
}

Trait Bounds in Implementations

RUST
impl<T: Display> Pair<T> {
    fn cmp_display(&self) {
        if self.x >= self.y {
            println!("The largest x = {}", self.x);
        }
    }
}

Conditionally Implement Methods

RUST
impl<T: Display + PartialOrd> Pair<T> {
    fn cmp_display(&self) {
        // ...
    }
}

Blanket Implementations

RUST
impl<T: Display> ToString for T {
    fn to_string(&self) -> String {
        // ...
    }
}

Code Examples

Basic Trait Bound

Using trait bounds

RUST
use std::fmt::Display;
fn print_largest<T: PartialOrd + Display>(list: &[T]) -> Option<&T> {
    if list.is_empty() {
        return None;
    }
    let mut largest = &list[0];
    for item in list {
        if item > largest {
            largest = item;
        }
    }
    Some(largest)
}
fn main() {
    let numbers = vec![34, 50, 25, 100, 65];
    if let Some(largest) = print_largest(&numbers) {
        println!("The largest number: {}", largest);
    }
}

Explanation:

The T: PartialOrd + Display trait bound ensures that type T is comparable and printable. This allows the use of the > operator and println!.

where Clause

Using where for complex trait bounds

RUST
use std::fmt::Display;
use std::fmt::Debug;
fn compare_and_print<T, U>(t: &T, u: &U)
where
    T: Display + PartialOrd,
    U: Display + Debug,
{
    println!("t: {}, u: {:?}", t, u);
}
fn main() {
    compare_and_print(&5, &"hello");
}

Explanation:

The where clause makes complex trait bounds more readable. Especially useful when there are many generic parameters.

Conditional Implementation

Implementing methods based on trait bounds

RUST
use std::fmt::Display;
struct Pair<T> {
    x: T,
    y: T,
}
impl<T> Pair<T> {
    fn new(x: T, y: T) -> Pair<T> {
        Pair { x, y }
    }
}
impl<T: Display + PartialOrd> Pair<T> {
    fn cmp_display(&self) {
        if self.x >= self.y {
            println!("The largest x = {}", self.x);
        } else {
            println!("The largest y = {}", self.y);
        }
    }
}
fn main() {
    let pair = Pair::new(5, 3);
    pair.cmp_display();
}

Explanation:

With trait bounds, we can conditionally implement methods. cmp_display() is only available if T implements Display and PartialOrd traits.

Exercises

Trait Bounds

Write a function with trait bounds!

Medium

Starter Code:

RUST
use std::fmt::Display;
fn main() {
    print_if_greater(&10, &5);
}