liveBook · Manning

Generics

As explained in Learn Rust in a Month of Lunches

A generic function is a Rust function parameterized by one or more type parameters and usable with multiple concrete types.

A function that takes an `i32` cannot take a different type in its place. Generics let one function work with multiple concrete types instead of making you write a separate function for each type. Rust still uses concrete types: it turns generic functions into concrete ones at compile time, with no extra runtime work. section 5.1 section 5.3

fn return_item<T>(item: T) -> T {
    println!("Here is your item.");
    item
}
 
fn main() {
    let item = return_item(5);
}

In `fn return_item<T>(item: T) -> T`, the angle brackets declare `T` as a type parameter. The same `T` on the input and output says the function returns the type it receives. The call with `5` supplies a concrete type. section 5.1

The name `T` is a convention, not a special word; you could choose another name. But you must declare it in angle brackets after the function name. Without that declaration, Rust looks for a concrete type with that name and reports that it cannot find it. section 5.1

A generic type cannot automatically perform every operation. For example, formatting a value with `{:?}` fails if Rust cannot tell whether its type implements `Debug`. A trait bound supplies that requirement: `fn print_item<T: Debug>(item: T)`. Adding bounds lets the function do more, but admits fewer types. section 5.1 section 5.3

use std::fmt::Debug;                           #1
 
fn print_item<T: Debug>(item: T) {             #2
    println!("Here is your item: {item:?}");
}
 
fn main() {
    print_item(5);
}

The `Debug` bound makes the formatting operation valid for any accepted `T`. section 5.1

Use separate parameters such as `T` and `U` when inputs may have different types. Each can have its own bounds; `+` combines bounds, and a `where` clause moves them below the signature when it gets crowded. Reusing `T` for two inputs instead says they have the same type. section 5.1 section 12.2.1

When one shared type matters

A function that compares and prints two values can use `fn print_maximum<T: PartialOrd + Display>(one: T, two: T)`. `PartialOrd` supports the comparison, `Display` supports printing, and the repeated `T` ties the inputs to one type. Writing `impl PartialOrd + Display` separately for each input does not establish that relationship. section 12.2.1

The same pattern appears in iterator methods that accept closures: a parameter such as `F` has a bound describing the closure it accepts. In a blanket trait implementation, `impl<T>` can cover every type when `T` has no bounds, or only types satisfying stated bounds. section 12.1 section 17.6

How Learn Rust in a Month of Lunches covers it

How deep it goes
Works through type-parameter syntax, compiler errors, trait bounds, multiple parameters, and the distinction from `impl Trait`.
Examples
Uses an item-returning function, `Debug` formatting feedback, a two-value comparison, and closure-taking iterator signatures.
What it assumes
Assumes familiarity with concrete function signatures but introduces type parameters and angle-bracket syntax from zero.
What you can do after
Build it from scratch: write functions that accept multiple concrete types while requiring the traits their operations need.
How it connects
Connects trait bounds, `where` clauses, `impl Trait`, closures, iterator methods, and blanket trait implementations.

Also explained in

Compare how 20 books explain generics →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Learn Rust in a Month of Lunches. The section links point to the parts of the book it draws on.