---
title: "15.1 The problem generics solve"
description: "Writing the same function for i32, then for char, then admitting defeat: the duplication that motivates generics, and what a type parameter is."
url: "https://learnrust.net/chapter-15/the-problem-generics-solve/"
last_updated: "2026-06-13"
---

# 15.1 The problem generics solve

Here's a problem you can feel before you can name. Write a function that returns the larger of two `i32`s:

```rust
fn largest_i32(a: i32, b: i32) -> i32 {
    if a > b { a } else { b }
}
```

Fine. Now you need the same thing for `f64`. And for `char`. And maybe `u8`. Each is the identical logic, `if a > b { a } else { b }`, with only the type changed:

```rust
fn largest_i32(a: i32, b: i32) -> i32 { if a > b { a } else { b } }
fn largest_f64(a: f64, b: f64) -> f64 { if a > b { a } else { b } }
fn largest_char(a: char, b: char) -> char { if a > b { a } else { b } }
```

Three functions, one idea, copied three times. This is exactly the duplication that lesson [2.5](https://learnrust.net/chapter-2/why-functions-are-useful/) taught functions to eliminate, except functions can't help here: the thing that varies isn't a value, it's the *type*. You can't pass a type as an argument to an ordinary function. **Generics** are how you do precisely that, and this lesson is about the problem clearly enough that the solution feels inevitable.

## Why you can't just use one function

Your instinct might be to write one function that takes "any type." The naive attempt fails:

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

What type goes in the blanks? It can't be `i32`, that rejects `char`. There's no built-in "any type" you can write there, and even if there were, the function body needs to work for whatever type shows up. The duplication is real, and copy-paste is the wrong fix for the reasons lesson [2.5](https://learnrust.net/chapter-2/why-functions-are-useful/) gave: three copies means three places to fix a bug, three places to update, three chances to let them drift apart.

> **Key insight**
>
> The repetition here is across *types*, not values. A function parameter lets one function body work for many *values* of a fixed type (`largest_i32` handles every `i32`). A generic lets one function body work for many *types*. Generics are to types what parameters are to values: a way to write the logic once and supply the specifics at the call. Once you see that parallel, the syntax is just notation for it.

## A type parameter

The fix is to give the function a **type parameter**: a stand-in name for a type that the caller fills in, the way an ordinary parameter is a stand-in for a value. By convention the name is a single uppercase letter, usually `T` (for "type"):

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

Read `<T>` after the function name as "this function is generic over some type `T`." Then `a: T` and `b: T` mean both arguments are that same type, and `-> T` means it returns that type. `T` isn't a real type; it's a placeholder the compiler replaces with the actual type at each call: `i32` when you call `largest(3, 7)`, `char` when you call `largest('a', 'z')`. One definition, every type.

There's a catch, and it's the most important thing in this lesson: **that function does not yet compile.** We'll meet the exact error in the next lesson, but the shape of the problem is already visible. Inside the body we wrote `a > b`, comparing two values of type `T`, and the compiler's reasonable objection is: how do you know an *arbitrary* `T` can be compared with `>`? You can compare `i32`s, but what if someone calls `largest` with a type that has no notion of "greater than"? The generic promises to work for *any* `T`, but the body assumes `T` supports `>`, and those two claims are in tension.

## The cliffhanger

That tension is the throughline of the next two chapters. Generics (this chapter) let you write code over an unknown type `T`. But the moment the body *does* anything with a `T` (compares it, prints it, adds it), you need a way to tell the compiler "`T` isn't truly any type; it's any type that can be compared" (or printed, or added). That promise is called a **trait bound**, and traits are chapter 16. So this chapter teaches the generics machinery on examples that only *store and move* values of type `T` (no comparing, no printing), where no bound is needed, and the `largest` function, which needs to compare, is deliberately left as the unfinished business that chapter 16 resolves in its first payoff lesson.

> **Author's note**
>
> It would be tidier to teach generics and traits as one chapter, and some courses do. We split them on purpose: generics are a syntax you can learn cleanly on "containers that just hold a `T`", and traits are a big enough idea to deserve their own chapter. The price is that `largest` hangs unfinished across the boundary. Treat that as a feature. The itch of "but I can't compare them yet" is exactly what makes the trait-bounds lesson land when it arrives.

## Quiz time

**Question #1**

What kind of repetition do generics remove that ordinary function parameters can't?

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

Repetition across *types*. A normal parameter lets one function handle many values of a single fixed type; a generic lets one function definition handle many *types*. When the only thing that varies between copies of a function is the type it operates on, generics let you write it once.

</details>

**Question #2**

In `fn first<T>(items: T) -> T`, what is `T`, and when does it become a concrete type?

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

`T` is a type parameter: a placeholder for a type that the caller supplies, named by convention with a single uppercase letter. It becomes concrete at each call site, where the compiler substitutes the actual type used (e.g. `i32` or `String`), as the next lessons show.

</details>

**Question #3**

Why doesn't `fn largest<T>(a: T, b: T) -> T { if a > b { a } else { b } }` compile, in plain terms?

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

The body uses `a > b`, which assumes `T` can be compared with `>`, but the signature claims `largest` works for *any* `T`, and not every type supports `>`. The compiler won't let the body assume a capability the type parameter doesn't promise. Fixing it requires a trait bound (chapter 16) that says "`T` is any type that can be ordered", which is the cliffhanger this chapter ends on.

</details>

Next lesson writes generic functions that the compiler *does* accept, the ones that only hold and pass values of type `T`, and meets the exact error that `largest` produces, confirming the diagnosis above.

## Sitemap

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