R Rust by Evidence PDF

Chapter 10 — Generics and Trait Bounds

Goal

Reuse one implementation across types while stating exactly which operations it needs.

A generic parameter is a type placeholder chosen by the caller. The function body must work for every type allowed by its bounds.

fn larger<T>(left: T, right: T) -> T {
    if left > right { left } else { right }
}

fn main() {
    println!("{}", larger(4, 9));
}

Prediction

Does this compile?

RevealB

Representative evidence:

error[E0369]: binary operation `>` cannot be applied to type `T`
  |
  |     if left > right { left } else { right }
  |        ---- ^ ----- T
  |
help: consider restricting type parameter `T` with trait `PartialOrd`

The call happens to use integers, but the function definition claims to accept every T. The body is checked against that claim.

Mental model

Working model: <T> says the type may vary. A bound such as T: PartialOrd states the capability the implementation requires.

Precise model: bounds participate in type checking and trait selection; they are not runtime tests. Rust usually monomorphizes generic code, producing concrete versions for the types used. PartialOrd is enough for > but does not promise that every pair is meaningfully ordered—for example, floating-point NaN is unordered. The bound should match the operation, not an imagined category such as “number.”

The smallest corrected function is:

fn larger<T: PartialOrd>(left: T, right: T) -> T {
    if left > right { left } else { right }
}

This consumes both arguments and returns one. If callers must keep both values, borrowing better matches the requirement:

fn larger_ref<'a, T: PartialOrd>(left: &'a T, right: &'a T) -> &'a T {
    if left > right { left } else { right }
}

The explicit 'a says the returned reference is valid only for the lifetime shared by both inputs. Lifetime elision cannot choose an input when a function borrows two values; bounds alone do not solve lifetime relationships.

Multiple bounds can be written inline:

fn show_larger<T: PartialOrd + std::fmt::Display>(left: T, right: T) {
    let winner = if left > right { left } else { right };
    println!("{winner}");
}

Or use a where clause when the signature becomes crowded:

fn show_larger<T>(left: T, right: T)
where
    T: PartialOrd + std::fmt::Display,
{
    let winner = if left > right { left } else { right };
    println!("{winner}");
}

These forms mean the same thing.

Simplest fix

Add T: PartialOrd, the one capability used by >. Do not add Clone, Copy, Debug, or 'static unless the implementation actually needs them. Excess bounds reject valid callers and make later changes harder.

Tradeoff alternative: accept one concrete type, such as i64, if that is all the program needs. Concrete code gives clearer error messages and avoids pretending reuse exists. Generalize only when a second real use case appears. If heterogeneous values must be stored together at runtime, a trait object may fit better than a generic collection, at the cost of dynamic dispatch and object-safety restrictions.

Mini challenge

Why does this fail, and what is the minimum bound?

fn repeat<T>(value: T) -> (T, T) {
    (value, value)
}

Answer

A is the smallest change that preserves this exact body:

fn repeat<T: Copy>(value: T) -> (T, T) {
    (value, value)
}

That is appropriate for cheaply copyable values such as integers. A broader ownership-oriented version uses cloning:

fn repeat<T: Clone>(value: T) -> (T, T) {
    (value.clone(), value)
}

Clone supports types such as String, but cloning may allocate or otherwise be expensive. The function makes that cost part of its contract. If the caller only needs two readers, returning or accepting references can avoid duplication entirely.

The evidence across this part points to one habit: encode the program's real choices and requirements, but no more. Use a struct for one product of fields, an enum for one of several cases, Option for absence, Result for explained failure, a trait for shared capability, and a generic only when the implementation truly works across types.