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
T: PartialOrdmakesitem > bestlegal.T: Copymakeslet mut best = items[0];andfor &item in itemslegal: both copy an element out of the slice. AStringcan't be copied, only moved, and you can't move out of a borrowed slice.largest_refavoids needingCopyby returning a reference to the largest element. That's why it works forStrings too. Fewer bounds means more types can use your function.- Every type in the calls satisfies the bounds: integers, floats and
chars can all be compared and copied.
(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
- A missing
Copybound. With onlyT: PartialOrd, the first version fails atitems[0]:
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
- Calling with a type that doesn't meet the bound.
largest(&names)withStrings givesthe trait bound `String: Copy` is not satisfied. The function's bounds are a promise the caller has to keep. Uselargest_refinstead. - Printing a
Twithout a bound.println!("{x}")with an unboundedTgives`T` doesn't implement `std::fmt::Display`. AddT: Display(anduse std::fmt::Display;).
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 "".