RustArena

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

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

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.

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