Generics

Generic Functions

A generic function is a function whose signature declares one or more type parameters inside angle brackets (<...>) immediately after the function name and before the parameter list. Those type parameters can then be used in parameter types, the return type, and local variable annotations inside the function body.

Declaration Syntax

The type parameter list sits between the function identifier and the opening parenthesis of the value parameters:

fn function_name<T, U>(param1: T, param2: U) -> ReturnType<T>
   ^^^^^^^^^^^^^ ^^^^  ^^^^^^^^^^^^^^^^^^^^     ^^^^^^^^^^^^^
   identifier    type  value parameters         return type
                 params                         using T

By convention, Rust uses single uppercase letters starting with T (for "type") for general-purpose type parameters, or descriptive UpperCamelCase names such as Item, Key, Value, or Err when role clarity matters.

fn make_pair<T>(left: T, right: T) -> (T, T) {
    (left, right)
}

fn main() {
    let ints = make_pair(3, 7);
    let labels = make_pair("start", "end");

    println!("ints: {ints:?}");
    println!("labels: {labels:?}");
}
ints: (3, 7)
labels: ("start", "end")

Because both left and right have type T in make_pair<T>, passing arguments of two different types is a compile-time type mismatch:

// ❌ does not compile
fn make_pair<T>(left: T, right: T) -> (T, T) {
    (left, right)
}

fn main() {
    let mixed = make_pair(10_i32, "ten");
}
error[E0308]: mismatched types
 --> src/main.rs:7:35
  |
7 |     let mixed = make_pair(10_i32, "ten");
  |                 --------- ------  ^^^^^ expected `i32`, found `&str`
  |                 |         |
  |                 |         expected all arguments to be this `i32` type because they need to match the type of this parameter
  |                 arguments to this function are incorrect

Constraining Type Parameters in Functions

An unconstrained type parameter T only supports moving, dropping, and taking references. To compare, clone, format, or compute with values of type T, the signature must constrain T with trait bounds:

fn max_element<T: PartialOrd>(slice: &[T]) -> Option<&T> {
    let mut best = slice.first()?;
    for item in &slice[1..] {
        if item > best {
            best = item;
        }
    }
    Some(best)
}

fn main() {
    let temps = [18.5, 24.2, 19.8, 21.0];
    let names = ["charlie", "alice", "bob"];

    println!("Max temp: {:?}", max_element(&temps));
    println!("Max name: {:?}", max_element(&names));
}
Max temp: Some(24.2)
Max name: Some("charlie")

Ownership and Borrowing in Generic Parameters

A generic parameter T represents a concrete type according to how it appears in the parameter list:

Parameter Signature Ownership Mode Example Concrete Substitution (T = String)
value: T Takes ownership of T (or copies if T: Copy) value: String
value: &T Borrows T immutably value: &String
value: &mut T Borrows T mutably value: &mut String
slice: &[T] Borrows a contiguous slice of T slice: &[String]

When a function takes a: &mut T and b: &mut T, it can swap or mutate the underlying values in place without requiring T: Clone or T: Copy:

fn rotate_three<T>(a: &mut T, b: &mut T, c: &mut T) {
    std::mem::swap(a, b);
    std::mem::swap(b, c);
}

fn main() {
    let mut first = String::from("alpha");
    let mut second = String::from("beta");
    let mut third = String::from("gamma");

    rotate_three(&mut first, &mut second, &mut third);
    println!("{first}, {second}, {third}");
}
beta, gamma, alpha

Explicit Type Arguments (Turbofish)

Most calls to generic functions let the compiler infer T from the arguments or return-type context. When a generic function's type parameter appears only in the return position or you want to specify the concrete type at the call site, use the turbofish syntax (::<ConcreteType>):

use std::str::FromStr;

fn parse_or_default<T>(input: &str) -> T
where
    T: FromStr + Default,
{
    input.parse::<T>().unwrap_or_default()
}

fn main() {
    let port = parse_or_default::<u16>("8080");
    let fallback = parse_or_default::<u16>("invalid");

    println!("port = {port}, fallback = {fallback}");
}
port = 8080, fallback = 0