RustArena

Step 3 of 6

Traits as parameters: bounds and impl Trait

The real payoff of a trait: functions that accept any type with that behavior. You already know the syntax from generics, where you wrote bounds like T: PartialOrd. A trait bound works the same with your own traits.

trait Summary {
    fn summarize(&self) -> String;
}

struct Article {
    title: String,
}

struct Post {
    user: String,
    text: String,
}

impl Summary for Article {
    fn summarize(&self) -> String {
        format!("Article: {}", self.title)
    }
}

impl Summary for Post {
    fn summarize(&self) -> String {
        format!("@{}: {}", self.user, self.text)
    }
}

fn preview(item: &impl Summary) {
    println!("Preview -> {}", item.summarize());
}

fn feed<T: Summary>(items: &[T]) {
    for item in items {
        println!("- {}", item.summarize());
    }
}

fn main() {
    let a = Article { title: String::from("Crabs can't jump") };
    let p = Post { user: String::from("ana"), text: String::from("first!") };
    preview(&a);
    preview(&p);

    let posts = vec![
        Post { user: String::from("bo"), text: String::from("gg") },
        Post { user: String::from("cy"), text: String::from("wp") },
    ];
    feed(&posts);
}
Preview -> Article: Crabs can't jump
Preview -> @ana: first!
- @bo: gg
- @cy: wp

Like all generics, this is monomorphized: the compiler writes a separate copy of preview for Article and one for Post. Calling through a trait bound is exactly as fast as calling the method directly.

One type, or many?

These two signatures are not the same:

fn both(a: &impl Summary, b: &impl Summary)      // a and b may be different types
fn both<T: Summary>(a: &T, b: &T)                // a and b must be the SAME type T

And feed(&[T]) takes a slice where every item is the same type T: a Vec can't mix Articles and Posts. Mixing types in one collection needs trait objects, a later module.

Common mistakes

trait Summary {
    fn summarize(&self) -> String;
}

fn feed<T>(items: &[T]) {
    for item in items {
        println!("{}", item.summarize());
    }
}

fn main() {}
error[E0599]: no method named `summarize` found for reference `&T` in the current scope
  = help: items from traits can only be used if the type parameter is bounded by the trait
help: the following trait defines an item `summarize`, perhaps you need to restrict type parameter `T` with it:
  |
5 | fn feed<T: Summary>(items: &[T]) {

Your task

A café's menu items implement Priced. Write two functions:

Challenge 2

Write fn cheaper(a: &impl Priced, b: &impl Priced) -> u32 that returns the lower of the two prices. Because each parameter has its own impl Priced, you can compare a coffee with a bagel.

Challenge 3

Write fn digest<T: Summary>(items: &[T]) -> String that numbers the summaries, one per line, like this for two posts:

1. @bo: gg
2. @cy: wp

Lines are separated by \n with no newline at the end. An empty slice gives an empty string.

Previous: Default methods Next: Implementing Display