RustArena

Step 2 of 6

Trait bounds: what T is allowed to do

Now a function that finds the largest element:

fn largest<T>(items: &[T]) -> T {
    let mut best = items[0];
    for &item in items {
        if item > best {
            best = item;
        }
    }
    best
}

fn main() {
    println!("{}", largest(&[1, 5, 2]));
}
error[E0369]: binary operation `>` cannot be applied to type `T`
  help: consider restricting type parameter `T` with trait `PartialOrd`

Inside a generic function, the compiler only allows what works for every possible T, and not every type can be compared with >. (How would you order two bank accounts, or two colors?) So you must say what T needs to be able to do.

Each capability is a trait: a named set of operations a type can support. Module 22 shows how to write your own; for now you only need to use a few from the standard library. A trait bound like T: PartialOrd restricts T to types that have that capability:

Bound Lets you
PartialEq compare with == and !=
PartialOrd compare with <, >, <=, >=
Copy copy values (let x = items[0];) instead of moving them: numbers, char, bool
Clone call .clone()
Display print with {} (it's std::fmt::Display)
Debug print with {:?} (it's std::fmt::Debug)

Several bounds are joined with +:

fn largest<T: PartialOrd + Copy>(items: &[T]) -> T {
    let mut best = items[0];
    for &item in items {
        if item > best {
            best = item;
        }
    }
    best
}

fn largest_ref<T: PartialOrd>(items: &[T]) -> &T {
    let mut best = &items[0];
    for item in items {
        if item > best {
            best = item;
        }
    }
    best
}

fn main() {
    println!("{}", largest(&[34, 50, 25, 100, 65]));
    println!("{}", largest(&[1.5, -2.0, 0.25]));
    println!("{}", largest(&['r', 'u', 's', 't']));
    let names = vec![String::from("ana"), String::from("zoe"), String::from("bo")];
    println!("{}", largest_ref(&names));
}
100
1.5
u
zoe

(Both versions panic on an empty slice, at items[0]. A more careful version returns an Option: that's one of the exercises.)

Display and Debug bounds

To print a T, require the matching trait:

use std::fmt::{Debug, Display};

fn announce<T: Display>(label: &str, value: T) {
    println!("{label}: {value}");
}

fn log_all<T: Debug>(items: &[T]) {
    for item in items {
        println!("[log] {item:?}");
    }
}

fn main() {
    announce("winner", "Ferris");
    announce("score", 9001);
    announce("ratio", 0.75);
    log_all(&[Some(1), None]);
}
winner: Ferris
score: 9001
ratio: 0.75
[log] Some(1)
[log] None

When bounds get long, Rust also lets you list them after the signature in a where clause (fn largest<T>(items: &[T]) -> T where T: PartialOrd + Copy). Module 22 covers where, writing your own traits, and more.

Common mistakes

fn largest<T: PartialOrd>(items: &[T]) -> T {
    let mut best = items[0];
    for &item in items {
        if item > best {
            best = item;
        }
    }
    best
}

fn main() {
    println!("{}", largest(&[1, 5, 2]));
}
error[E0508]: cannot move out of type `[T]`, a non-copy slice

Your task

Write clamp, which keeps a value between a lower and an upper limit: below low it returns low, above high it returns high, otherwise the value itself. Add the trait bound T needs. It should work for numbers, chars and &strs.

Challenge 2

Write fn smallest<T: PartialOrd + Copy>(items: &[T]) -> Option<T>: the smallest element, or None for an empty slice (no panic).

Challenge 3

Write join_all, which turns a slice of anything printable into one String, with separator between the items: join_all(&[1, 2, 3], ", ") is "1, 2, 3". Add the bound it needs (and the use line for it). An empty slice gives "".

Previous: Generic functions Next: Generic structs and enums