Skip to content

← Generics and Traits step 4 of 24

Easy Primitives

where clauses and the bound you cannot write inline

A trait bound is a requirement on a type, such as T: Display, which says "T must implement the Display trait". Rust lets you write bounds in two places. These two signatures are equivalent:

pub fn summarise<T: Display>(items: Vec<T>) -> String

pub fn summarise<T>(items: Vec<T>) -> String
where
    T: Display,

The first puts the bound inline, next to the generic parameter in the angle brackets. The second puts it in a where clause after the signature. For a simple bound like this one, the choice is about readability.

What a where clause adds

An inline bound is attached to a generic parameter you declare inside <...>. A where clause can put a bound on any type, including types built from those parameters:

pub fn copy_all<T>(v: &Vec<T>) -> Vec<T>
where
    Vec<T>: Clone,   // a bound on Vec<T>, not on T
{
    v.clone()
}

There is no inline position for Vec<T>: Clone, because Vec<T> is not a parameter in the angle brackets.

A related case is an associated type: a type that a trait implementation chooses. IntoIterator has one called Item, the type of the values you get when you loop over the collection. If I: IntoIterator, then I::Item is that type. You cannot list I::Item as a parameter in the angle brackets (<I: IntoIterator, I::Item: Display> is a syntax error), but you can bound it in a where clause:

where
    I: IntoIterator,
    I::Item: Display,

Since Rust 1.79 you can also bound it inline, nested inside I's own bound: <I: IntoIterator<Item: Display>>. Both forms mean the same thing and both pass this exercise. This exercise uses the where form.

Your task

pub fn summarise<T>(items: Vec<T>) -> String
where
    T: Display,

pub fn summarise_any<I>(items: I) -> String
where
    I: IntoIterator,
    I::Item: Display,

pub fn run(nums: Vec<i64>, words: Vec<String>) -> Vec<String>

Both summarisers produce the same format: the number of items, the text " items: ", then each item's Display output in input order, separated by ", ".

  • [1, 2, 3] gives "3 items: 1, 2, 3".
  • The word is always items, even for one item: [-42] gives "1 items: -42".
  • An empty input gives "0 items: ", with a trailing space, because the same format string is used for every length.
  • Numbers use the normal decimal form of i64, with a leading - for negatives. Strings appear exactly as given, with no quotes or escaping, so ["", ""] gives "2 items: , ". Non-ASCII text such as "héllo" is copied unchanged.

run returns [summarise(nums), summarise_any(words)]. The tests only call run, but summarise_any should accept any iterable whose items implement Display, not just Vec<String>.

The two problems in the starter

The grade requires the code to compile, pass the tests and pass cargo clippy -- -D warnings, which turns every Clippy warning into an error. The starter fails for two reasons.

1. A bound is written in two places. summarise has <T: Display> and also where T: Display. The compiler accepts that, but Clippy's multiple_bound_locations lint, which is enabled by default, reports it:

error: bound is defined in more than one place
  |
2 | pub fn summarise<T: Display>(items: Vec<T>) -> String
  |                  ^
3 | where
4 |     T: Display,
  |     ^

The lint fires whenever one parameter has bounds in both locations, even if the two bounds name different traits. Keep one location and delete the other.

2. summarise_any does not know what its items are. I: IntoIterator says the argument can be looped over, but says nothing about the type of each item. The body calls .to_string() on each item, which needs Display, so the compiler rejects it:

error[E0599]: `<I as std::iter::IntoIterator>::Item` doesn't implement `std::fmt::Display`
  = note: the following trait bounds were not satisfied:
          `<I as std::iter::IntoIterator>::Item: std::fmt::Display`
          which is required by `<I as std::iter::IntoIterator>::Item: std::string::ToString`

<I as std::iter::IntoIterator>::Item is the fully written-out name of I::Item: the Item type of I's IntoIterator implementation. The fix is to add the bound the note asks for.

Clippy lints you will not see here

Clippy also has type_repetition_in_bounds and trait_duplication_in_bounds, which look for repeated bounds. Both are in the nursery group, which is disabled by default, so they do not run in this grader. The lint that affects this exercise is multiple_bound_locations.