Step 5 of 6
Multiple bounds, where clauses and returning impl Trait
A type parameter can need several traits at once. Join them with +: T: Display + PartialOrd means "T can be printed with {} and compared with < and >". When the bounds get long, move them into a where clause after the signature, so the function's name and parameters stay readable.
use std::fmt::Display;
fn announce_max<T>(items: &[T]) -> String
where
T: Display + PartialOrd,
{
let mut best = &items[0];
for item in items {
if item > best {
best = item;
}
}
format!("winner: {best}")
}
fn main() {
println!("{}", announce_max(&[3, 9, 4]));
println!("{}", announce_max(&["pear", "apple", "plum"]));
println!("{}", announce_max(&[1.5, 0.25]));
}
winner: 9
winner: plum
winner: 1.5
PartialOrdmakesitem > bestlegal;Displaymakes{best}legal. Remove either one and that line stops compiling.- The
whereclause says the same asfn announce_max<T: Display + PartialOrd>(items: &[T]). Pick whichever reads better. Teams usually switch towhereonce there are two or more type parameters or more than a couple of bounds, andrustfmtlays it out exactly as above. - Strings compare alphabetically, so
"plum"beats"pear"and"apple".
Several type parameters, each with its own bounds:
fn log_pair<K, V>(key: K, value: V) -> String
where
K: Display,
V: Debug + Clone,
Returning impl Trait
impl Trait also works in the return type: "this function returns some type that implements the trait, and that's all you need to know".
use std::fmt::Display;
fn badge(points: u32) -> impl Display {
format!("[{points} pts]")
}
fn main() {
let b = badge(120);
println!("Ferris {b}");
}
Ferris [120 pts]
The caller can only use b as a Display: it can't call String methods on it, even though it really is a String. That's the point: the function is free to change the concrete type later without breaking callers. You'll use this most with iterators and closures, whose real types are long or can't even be written down.
Common mistakes
- Returning different types from
-> impl Trait.impl Traitstands for one concrete type, chosen by the function body. Two branches with different types don't compile:
use std::fmt::Display;
fn label(n: u32) -> impl Display {
if n == 0 { "none" } else { n }
}
fn main() {
println!("{}", label(3));
}
error[E0308]: `if` and `else` have incompatible types
Here the fix is to return the same type from both branches (n.to_string() and String::from("none")). The compiler also suggests Box<dyn Display>; that's a trait object, coming up in a later module.
- A missing bound. If you compare with
>but only wroteT: Display, you geterror[E0369]: binary operation `>` cannot be applied to type `&T`, with a help line suggestingT: PartialOrd.
Your task
Write fn describe_range<T>(items: &[T]) -> String that returns the smallest and largest item as min..max, for example 2..9. An empty slice returns empty. Put the bounds (Display and PartialOrd) in a where clause.
Challenge 2
Write fn count_matches<T>(items: &[T], target: &T) -> String that counts how many items equal target and returns <count> x <target>, for example 3 x apple. Use a where clause with the two bounds you need (one for ==, one for printing).
Challenge 3
Write fn breaking_news(headline: &str) -> impl Summary that returns an Article with that title and the author Newsroom. Callers only know they get "something that implements Summary", so the tests can only call summarize() on it.
Previous: Implementing Display Next: Deriving the standard traits