---
title: "15.2 Generic functions"
description: "Writing functions generic over a type with <T>, the kinds of generic code that compile without bounds, and the exact error you get when the body needs more."
url: "https://learnrust.net/chapter-15/generic-functions/"
last_updated: "2026-06-13"
---

# 15.2 Generic functions

The last lesson introduced the type parameter `<T>` and ended on a function that doesn't compile. This lesson writes generic functions that *do*, sees exactly what kind of generic code the compiler accepts on its own, and then meets the error that `largest` produces, which sets up chapter 16.

## The syntax

A generic function declares its type parameters in angle brackets after the name, then uses them in the parameter and return types:

```rust
fn first_of_pair<T>(pair: (T, T)) -> T {
    pair.0
}

fn main() {
    let a = first_of_pair((1, 2));
    let b = first_of_pair(('x', 'y'));
    println!("{a} {b}");
}
```

```
1 x
```

`first_of_pair` is generic over `T`. It takes a pair of `T`s and returns the first one. The compiler figures out what `T` is at each call: `i32` for `(1, 2)`, `char` for `('x', 'y')`. You wrote the logic once, and it serves both. You can have several type parameters, too: `fn pair<T, U>(a: T, b: U) -> (T, U)` is generic over two independent types.

This compiles with no fuss because of *what the body does*: it only moves a value around (`pair.0` hands back one of the values). It never compares, prints, or computes with the `T`. That's the key to which generics need nothing extra.

## What compiles without bounds

A generic function compiles on its own as long as its body only does things that work for *every possible type*: storing a value, moving it, returning it, putting it in a tuple, passing it to another function that's equally generic. These operations don't care what `T` is, so the compiler is satisfied that the function truly works for any `T`.

```rust
fn swap<T>(a: T, b: T) -> (T, T) {
    (b, a)
}

fn triple<T: Clone>(x: T) -> (T, T, T) {
    (x.clone(), x.clone(), x)
}
```

`swap` just reorders two values; it works for any `T`, no bound needed. `triple` is the first hint of what's coming: it calls `x.clone()`, and *cloning* isn't something every type can do (lesson [8.7](https://learnrust.net/chapter-8/clone/)), so it must say `T: Clone`, read "`T` is some type that can be cloned." That `: Clone` is a **trait bound**, a promise about `T`'s capabilities, and the full story is chapter 16. We show it here only so you recognize that the moment a generic body *does something* to a `T`, a bound usually appears.

## The largest error, at last

Now the function from lesson [15.1](https://learnrust.net/chapter-15/the-problem-generics-solve/), and the exact error it produces:

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

```
error[E0369]: binary operation `>` cannot be applied to type `T`
 --> src/main.rs:2:10
  |
2 |     if a > b { a } else { b }
  |        - ^ - T
  |        |
  |        T
  |
help: consider restricting type parameter `T`
  |
1 | fn largest<T: std::cmp::PartialOrd>(a: T, b: T) -> T {
  |             ++++++++++++++++++++++
```

There it is, precisely the diagnosis from last lesson. The body uses `>` on two `T`s, and the compiler objects: a bare `T` is *any* type, and not every type supports `>`. Read the `help` block as the compiler reading ahead in the syllabus (the same way it pointed at `&mut` in lesson [9.1](https://learnrust.net/chapter-9/references-borrowing-a-value/)): it suggests `T: std::cmp::PartialOrd`, a trait bound saying "restrict `T` to types that can be ordered." `PartialOrd` is the trait for the comparison operators, and adding that bound is exactly what chapter 16 does to make `largest` compile. The error message is, quite literally, the table of contents for the next chapter.

> **Key insight**
>
> A generic function compiles without bounds only while its body treats `T` as an opaque thing to move around. The instant the body *uses* a capability (`>`, `==`, `+`, printing, cloning), the compiler demands a bound proving `T` has that capability. This is the line between this chapter and the next: generics give you the `<T>` placeholder; trait bounds (chapter 16) let the body actually *do* things with it. Until then, generic functions in this chapter only store and pass values.

## Turbofish: naming the type explicitly

Usually the compiler infers `T` from the arguments. When it can't (because nothing in the call pins it down), you name it explicitly with the **turbofish** `::<T>`, the syntax you first glimpsed with `parse::<i32>()` in lesson [5.6](https://learnrust.net/chapter-5/parsing-strings-into-numbers/):

```rust
fn make_default<T: Default>() -> T {
    T::default()
}

fn main() {
    let x = make_default::<i32>();   // turbofish: T is i32
    let y: f64 = make_default();     // or let the binding's type decide
    println!("{x} {y}");
}
```

```
0 0
```

`make_default` returns a `T` but takes no `T` argument, so the compiler can't infer the type from a parameter; you supply it with `make_default::<i32>()` or by annotating the binding (`let y: f64 = ...`). The turbofish is the explicit answer to "which type?" when inference can't decide, and `parse` was a generic function all along, which is why it needed the turbofish or a type annotation back in chapter 5. (`make_default` uses a `Default` bound, chapter 16 again, because calling `T::default()` requires `T` to have a default.)

## Quiz time

**Question #1**

Why does `fn swap<T>(a: T, b: T) -> (T, T) { (b, a) }` compile without any trait bound, while `largest` does not?

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

`swap` only *moves* its values (it reorders them into a tuple), and moving works for every type, so no capability needs to be promised. `largest` uses `a > b`, which requires `T` to support comparison, and not every type does, so the compiler demands a bound (`T: PartialOrd`). The body's operations decide whether a bound is needed.

</details>

**Question #2**

What error does `largest` produce, and what does the compiler's `help` suggest?

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

E0369: binary operation `>` cannot be applied to type `T`. The `help` suggests restricting the type parameter with a trait bound: `fn largest<T: std::cmp::PartialOrd>(...)`. That bound (chapter 16) promises `T` supports ordering, which is what the `>` in the body needs.

</details>

**Question #3**

When do you need the turbofish (`::<T>`), and where have you used it before?

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

When the compiler can't infer the type parameter from the call's arguments, typically because the type appears only in the return type (or nowhere in the parameters). You supply it explicitly with `func::<Type>()`, or by annotating the binding's type. You used it with `parse::<i32>()` in lesson [5.6](https://learnrust.net/chapter-5/parsing-strings-into-numbers/); `parse` was a generic function all along.

</details>

Generic *functions* take a type parameter. So can structs and enums, and the next lesson reveals that two types you've used since chapter 11, `Option<T>` and `Vec<T>`, were generic structs and enums the whole time.

## Sitemap

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