Generics

Multiple Type Parameters

A function, struct, enum, or trait can declare multiple type parameters separated by commas inside a single pair of angle brackets (<T, U, V>). Each type parameter is independent and can be instantiated with the same concrete type or with different concrete types at each usage site.

Structs with Multiple Type Parameters

When fields of a struct can hold different types independently, assign a distinct type parameter to each independent role:

#[derive(Debug)]
struct KeyValue<K, V> {
    key: K,
    value: V,
}

fn main() {
    let header = KeyValue {
        key: "Content-Length",
        value: 1024_usize,
    };
    let same_types = KeyValue {
        key: "host",
        value: "localhost",
    };

    println!("header: {:?} -> {}", header.key, header.value);
    println!("same_types: {:?} -> {}", same_types.key, same_types.value);
}
header: "Content-Length" -> 1024
same_types: "host" -> localhost

Notice that KeyValue<K, V> permits K and V to differ (&str and usize) as well as match (&str and &str).

Functions with Multiple Type Parameters

Functions that combine, transform, or convert between distinct types declare multiple parameters in their <...> list and constrain each parameter according to its role:

use std::fmt::{Debug, Display};

fn format_tagged<K, V>(key: K, value: V) -> String
where
    K: Display,
    V: Debug,
{
    format!("{key} = {value:?}")
}

fn main() {
    let summary = format_tagged("retries", [100_u32, 200, 400]);
    println!("{summary}");
}
retries = [100, 200, 400]

Parameter Ordering and Partial Turbofish (_)

When calling a function with multiple type parameters using turbofish syntax (::<...>), the arguments must match the declaration order. You can supply explicit types for the parameters that cannot be inferred and write _ for parameters the compiler can deduce from function arguments:

use std::str::FromStr;

fn parse_field<K, V>(key: K, raw_value: &str) -> Result<(K, V), V::Err>
where
    V: FromStr,
{
    let parsed = raw_value.parse::<V>()?;
    Ok((key, parsed))
}

fn main() {
    // K is inferred as &str via `_`, while V is explicitly specified as u32:
    let entry = parse_field::<_, u32>("max_connections", "512").unwrap();
    println!("{} => {}", entry.0, entry.1);
}
max_connections => 512

Supplying fewer type arguments than declared in <K, V> without defaults produces error E0107:

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

fn main() {
    let pair = combine::<i32>(10, "ten");
}
error[E0107]: function takes 2 generic arguments but 1 generic argument was supplied
 --> src/main.rs:7:16
  |
7 |     let pair = combine::<i32>(10, "ten");
  |                ^^^^^^^------- supplied 1 generic argument
  |                |
  |                expected 2 generic arguments
  |
note: function defined here, with 2 generic parameters: `T`, `U`
 --> src/main.rs:2:4
  |
2 | fn combine<T, U>(left: T, right: U) -> (T, U) {
  |    ^^^^^^^ -  -
help: add missing generic argument
  |
7 |     let pair = combine::<i32, U>(10, "ten");
  |                             +++

Avoiding Excessive Type Parameter Proliferation

While Rust places no hard limit on the number of type parameters, structs or functions with more than three or four type parameters become difficult to read and annotate. Group related types into smaller domain structs or use trait associated types when one type uniquely determines another:

Pattern Signature Ergonomics
Unrelated independent types struct Either<L, R> Two parameters; both roles are clear
Excessive flat parameters struct Request<H, B, Q, A, E> Hard to annotate; every wrapper repeats 5 types
Grouped domain components struct Request<Body> { meta: Meta, body: Body } Only the varying payload remains generic