---
title: "16.3 Trait bounds"
description: "Trait bounds restrict a generic type parameter to types that implement a trait, finally making largest compile; the T: Trait syntax, where clauses, and multiple bounds."
url: "https://learnrust.net/chapter-16/trait-bounds/"
last_updated: "2026-06-13"
---

# 16.3 Trait bounds

This is the payoff. Chapter 15 left `largest` unfinished: a generic function whose body compared two `T`s with `>`, which the compiler rejected because not every type can be compared. Traits are the missing piece. A **trait bound** restricts a type parameter to types that implement a given trait, which tells the compiler exactly what `T` can do, and the moment we add the right bound, `largest` compiles.

## The syntax: T: Trait

A trait bound is written with a colon after the type parameter: `T: Trait` means "`T` is some type that implements `Trait`." Here is `largest`, finally complete:

```rust
fn largest<T: PartialOrd>(a: T, b: T) -> T {
    if a > b { a } else { b }
}

fn main() {
    println!("{}", largest(3, 7));
    println!("{}", largest('a', 'z'));
    println!("{}", largest(2.5, 1.0));
}
```

```
7
z
2.5
```

The only change from chapter 15's broken version is `<T: PartialOrd>` instead of `<T>`. `PartialOrd` is the standard-library trait for the comparison operators (`<`, `>`, `<=`, `>=`), so `T: PartialOrd` promises "`T` is a type you can compare with `>`." Now the body's `a > b` is justified: the bound guarantees every `T` this function is ever called with supports comparison, and the compiler checks at each call that the type actually implements `PartialOrd`. Call `largest` with `i32`, `char`, `f64`, all of which implement `PartialOrd`, and it works; call it with a type that doesn't, and *that call* is rejected, with a clear message, rather than the function definition being broken.

This is exactly what chapter 15's E0369 error and its `help` line predicted. The compiler told you to add `T: PartialOrd`; you added it; the function compiles. The cliffhanger is resolved.

> **Key insight**
>
> A trait bound is the bridge between generics and traits. Generics give you a type parameter `T`; a trait bound tells the compiler what `T` can *do*, which is what lets the body actually use it. Without bounds, a generic body can only move values around (chapter 15); with bounds, it can compare them, print them, add them, anything the bounding traits provide. "Generic over any type that can be compared" is a far more useful thing to say than "generic over any type," and trait bounds are how you say it.

## Multiple bounds and where clauses

A type parameter can require several traits at once, joined with `+`. Suppose a function needs to both print and compare its values:

```rust
use std::fmt::Display;

fn largest_and_announce<T: PartialOrd + Display>(a: T, b: T) -> T {
    let winner = if a > b { a } else { b };
    println!("the winner is {winner}");
    winner
}
```

`T: PartialOrd + Display` means "`T` implements *both* `PartialOrd` (so we can compare) *and* `Display` (so we can print with `{}`)." The body uses both capabilities, so it needs both bounds. When bounds pile up and the signature gets crowded, you can move them into a **`where` clause** after the return type, which many find more readable:

```rust
fn process<T, U>(a: T, b: U) -> String
where
    T: Display + PartialOrd,
    U: Clone + Display,
{
    format!("{a} and {b}")
}
```

The `where` clause says the same thing as inline bounds but keeps the `fn process<T, U>(...)` line uncluttered. Use inline bounds for one or two simple constraints, a `where` clause when there are several or they're long. They're interchangeable; it's purely a readability choice.

## Bounds on generic types and methods

Bounds aren't just for functions. A generic `impl` block can require a bound, so a method exists only when the type parameter supports it, the more general version of lesson [15.4](https://learnrust.net/chapter-15/generic-methods/)'s `impl Point<f64>` trick:

```rust
struct Pair<T> {
    first: T,
    second: T,
}

impl<T: PartialOrd + Display> Pair<T> {
    fn print_larger(&self) {
        if self.first >= self.second {
            println!("larger: {}", self.first);
        } else {
            println!("larger: {}", self.second);
        }
    }
}
```

`Pair<T>` exists for any `T`, but `print_larger` is defined only in an `impl` block bounded by `PartialOrd + Display`. So a `Pair<i32>` has `print_larger` (i32 is both comparable and printable), while a `Pair` of some type that isn't comparable simply wouldn't have that method. This is how the standard library gives `Vec` methods that appear only when the elements support them: a bounded `impl`.

## Bounds make the error message better, too

A subtle benefit: bounds move errors to the right place. With `largest<T: PartialOrd>`, if you call it with a non-comparable type, the error points at *your call* ("this type doesn't implement `PartialOrd`"), which is where the mistake is. Without the generic, you'd get a confusing error deep inside the function body. Bounds are part of the function's contract (lesson [13.8](https://learnrust.net/chapter-13/reading-documentation/)): the signature now states the requirement, so the caller is told upfront what kinds of `T` are allowed, exactly as a parameter type tells them what values are allowed.

## Quiz time

**Question #1**

What does the bound in `fn largest<T: PartialOrd>(...)` promise, and why does it make the `a > b` in the body legal?

<details class="solution">
<summary>Show solution</summary>

It promises that `T` implements `PartialOrd`, the trait for comparison operators, so every type `largest` is called with can be compared with `>`. That justifies `a > b` in the body: the compiler knows the operation is available for any allowed `T`, and it checks at each call site that the actual type implements `PartialOrd`.

</details>

**Question #2**

Write the bound for a generic function whose `T` must be both printable with `{}` and cloneable. Show both the inline form and the `where` form.

<details class="solution">
<summary>Show solution</summary>

Inline: `fn f<T: std::fmt::Display + Clone>(x: T) { ... }`. With a `where` clause:

```rust
fn f<T>(x: T)
where
    T: std::fmt::Display + Clone,
{ ... }
```

Both require `T: Display + Clone`. Multiple traits are joined with `+`; the `where` form is preferred when the bounds are numerous or long.

</details>

**Question #3**

How does a trait bound improve the error message when a function is misused, compared to a non-generic function with the operation buried inside?

<details class="solution">
<summary>Show solution</summary>

The bound is part of the signature, so calling the function with a type that doesn't satisfy the bound produces an error at the *call site* ("this type doesn't implement the required trait"), pointing right at the mistake. Without the bound stated up front, the error would surface deep in the function body where the operation is used, which is harder to connect back to the bad call.

</details>

Sometimes writing `<T: Trait>` is more ceremony than a simple case deserves. The next lesson covers `impl Trait`, a lighter syntax for "some type that implements this trait," useful in both argument and return position.

## Sitemap

See the full [sitemap](https://learnrust.net/sitemap.md) for all pages.
