Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Generics

Generics let one definition work with many types. A generic definition uses a type parameter such as T instead of naming one concrete type.

fn identity<T>(value: T) -> T {
    value
}

fn main() {
    let number = identity(42);
    let text = identity("hello");

    println!("{number} {text}");
}

The <T> after the function name declares a type parameter. Within the function, T stands for one concrete type selected for each call.

identity(42)      uses T = i32
identity("hello") uses T = &str

The two calls use the same function definition, but T is consistent within each call. If the input is an i32, the output is also an i32.

Type parameters express relationships

The useful part of a generic signature is often the relationship between its positions:

fn choose<T>(first: T, second: T, use_first: bool) -> T {
    if use_first {
        first
    } else {
        second
    }
}

fn main() {
    let selected = choose("left", "right", true);
    println!("{selected}");
}

Both arguments and the return value use T. The function requires the two arguments to have the same type and promises to return that type.

This call fails because the arguments have different types:

fn choose<T>(first: T, second: T, use_first: bool) -> T {
    if use_first { first } else { second }
}
fn main() {
let selected = choose(10, "ten", true);
}

Rust cannot choose one T that is both an integer and &str.

Use more than one parameter when the types may differ:

fn pair<T, U>(first: T, second: U) -> (T, U) {
    (first, second)
}

fn main() {
    let value = pair("level", 3);
    println!("{}: {}", value.0, value.1);
}

Here T is &str and U is i32.

Generic functions cannot do everything

The body of an unconstrained generic function knows almost nothing about T:

#![allow(unused)]
fn main() {
fn print_value<T>(value: T) {
    println!("{value}");
}
}

Not every possible T implements display formatting, so Rust rejects the definition. The function must state that requirement with a trait bound:

use std::fmt::Display;

fn print_value<T: Display>(value: T) {
    println!("{value}");
}

fn main() {
    print_value(42);
    print_value("hello");
}

Read T: Display as “any type T that implements Display.” The next chapter covers traits and bounds in detail.

Generic structs

A struct can be generic over the types stored in its fields:

#[derive(Debug)]
struct Point<T> {
    x: T,
    y: T,
}

fn main() {
    let integer = Point { x: 2, y: 5 };
    let decimal = Point { x: 1.5, y: 3.0 };

    println!("{integer:?}");
    println!("{decimal:?}");
}

Point<i32> and Point<f64> are different concrete types produced from the same definition. Because both fields use T, x and y must have the same type in each value.

Use separate parameters when the fields may differ:

struct Pair<T, U> {
    first: T,
    second: U,
}

fn main() {
    let pair = Pair {
        first: "height",
        second: 180,
    };

    println!("{}: {}", pair.first, pair.second);
}

Generic methods

Place the generic declaration after impl when defining methods for every Point<T>:

struct Point<T> {
    x: T,
    y: T,
}

impl<T> Point<T> {
    fn x(&self) -> &T {
        &self.x
    }
}

fn main() {
    let point = Point { x: 2, y: 5 };
    println!("{}", point.x());
}

The first <T> declares the parameter. The T in Point<T> uses it:

impl<T> Point<T>
     ▲        ▲
  declare     use

A method can introduce additional type parameters of its own:

struct Pair<T, U> {
    first: T,
    second: U,
}

impl<T, U> Pair<T, U> {
    fn replace_second<V>(self, value: V) -> Pair<T, V> {
        Pair {
            first: self.first,
            second: value,
        }
    }
}

fn main() {
    let pair = Pair {
        first: "level",
        second: 3,
    };
    let pair = pair.replace_second("three");

    println!("{}: {}", pair.first, pair.second);
}

The method consumes Pair<T, U> and returns Pair<T, V>, preserving the first field’s type while changing the second.

Methods can target one concrete version

Not every method must exist for every T:

struct Point<T> {
    x: T,
    y: T,
}

impl Point<f64> {
    fn distance_from_origin(&self) -> f64 {
        (self.x.powi(2) + self.y.powi(2)).sqrt()
    }
}

fn main() {
    let point = Point { x: 3.0, y: 4.0 };
    println!("{}", point.distance_from_origin());
}

distance_from_origin is available on Point<f64>, where the required floating-point operations are known. It is not available on Point<String>.

Trait bounds provide a more flexible way to make methods available whenever a type supports the required operations.

Generic enums

Standard-library enums use generics extensively:

#![allow(unused)]
fn main() {
enum Option<T> {
    Some(T),
    None,
}

enum Result<T, E> {
    Ok(T),
    Err(E),
}
}

Option<T> represents either one value of type T or no value. Result<T, E> represents either a success of type T or an error of type E.

The generic parameters let these enums describe many concrete types:

fn main() {
    let port: Option<u16> = Some(8080);
    let name: Option<&str> = None;
    let parsed: Result<i32, _> = "42".parse();

    println!("{port:?} {name:?} {parsed:?}");
}

Type inference usually fills in the arguments

Rust often infers generic arguments from function inputs or expected output:

fn main() {
    let numbers: Vec<i32> = (1..=3).collect();
    println!("{numbers:?}");
}

collect can build many collection types. The annotation Vec<i32> tells it which one to produce.

You can also supply a generic argument explicitly with ::<>, informally called the turbofish:

fn main() {
    let numbers = (1..=3).collect::<Vec<i32>>();
    println!("{numbers:?}");
}

Prefer inference when the surrounding code already makes the type clear. Use an annotation or turbofish when Rust needs help or when the type is useful to a reader.

Const generics parameterize values

Const generics use a compile-time value instead of a type. This function accepts a reference to an array of any length:

fn length<T, const N: usize>(_: &[T; N]) -> usize {
    N
}

fn main() {
    println!("{}", length(&[10, 20, 30]));
    println!("{}", length(&['a', 'b']));
}

T is a type parameter. N is a usize value known at compile time. The array length is part of the array’s type, so [i32; 3] and [i32; 4] are distinct types.

Const generics are useful for fixed-size arrays and data structures whose size is part of their contract.

Generics have no inherent runtime type lookup

Rust normally compiles generic code through monomorphization. The compiler generates concrete code for the versions the program uses, such as identity::<i32> and identity::<&str>.

This provides static type checking and usually avoids runtime dispatch. The tradeoff is that many concrete versions can increase compile time and binary size.

Generics are most useful when the same operation is genuinely valid across multiple types. If only one type makes sense, a generic parameter adds complexity without adding flexibility.