Generics

Type Parameters and Inference

Type inference for generics is the compile-time process by which the Rust compiler deduces concrete types for generic type parameters (<T>) from value arguments, variable annotations, return types, and subsequent method calls.

Hindley-Milner Inference Flow

When a generic item is instantiated without explicit type arguments, the compiler creates an internal type variable (?T) and collects unification constraints from three directions:

  1. Value Arguments        2. Later Operations       3. Expected Return Type
     vec.push(42_u16)          vec[0].count_ones()       let out: Vec<u16> = ...
            \                         |                         /
             +------------------------+------------------------+
                                      |
                                      v
                           Unifies ?T === u16
  1. Argument-driven inference: Passing 42_u16 to Vec::push(&mut self, value: T) constrains T to u16.
  2. Usage-driven inference: Calling Vec::new() creates a Vec<?T> whose element type remains unknown until a subsequent vec.push("hello") unifies ?T with &str.
  3. Return-type-driven inference: Calling .collect() or .parse() infers the target type from the left-hand binding annotation or the enclosing function's return signature.
fn main() {
    // 1. Inferred from later usage:
    let mut stack = Vec::new();
    stack.push(100_i64);
    println!("stack: {stack:?}");

    // 2. Inferred from binding type annotation:
    let parsed: i32 = "256".parse().unwrap();
    println!("parsed: {parsed}");

    // 3. Inferred from function return type:
    let collected = collect_Positive(&[-3, 4, 0, 9]);
    println!("collected: {collected:?}");
}

#[allow(non_snake_case)]
fn collect_Positive(nums: &[i32]) -> Vec<i32> {
    nums.iter().copied().filter(|&n| n > 0).collect()
}
stack: [100]
parsed: 256
collected: [4, 9]

The Turbofish Operator (::<...>)

In expressions, < is parsed as the less-than comparison operator unless preceded by ::. The turbofish token ::<...> disambiguates generic type arguments inside expression paths:

Expression Syntax Form Meaning
Vec::<u8>::with_capacity(16) Type path turbofish Calls associated function on Vec<u8>
"42".parse::<u32>() Method call turbofish Specifies F = u32 on str::parse<F>
std::mem::size_of::<usize>() Free function turbofish Specifies T = usize on size_of<T>
Option::<String>::None Enum variant turbofish Constructs None with T = String

In type annotations (such as let v: Vec<u8> or fn f(v: Vec<u8>)), < is unambiguous, so Vec<u8> is written without ::.

Using Type Placeholders (_) in Annotations

When a generic type has outer structure you want to specify (such as Vec<_> or Result<_, _>) while leaving inner element types to inference, use the type placeholder _:

fn main() {
    let squares: Vec<_> = (1..=5).map(|n| n * n).collect();
    println!("squares: {squares:?}");

    let results: Result<Vec<_>, _> = ["10", "20", "30"]
        .into_iter()
        .map(|s| s.parse::<u16>())
        .collect();
    println!("results: {results:?}");
}
squares: [1, 4, 9, 16, 25]
results: Ok([10, 20, 30])

Ambiguous Inference (E0282 and E0283)

When a generic return type is consumed immediately by another generic method (such as println!("{:?}", "42".parse().unwrap())), multiple concrete types implement FromStr and Debug, leaving the intermediate type unconstrained:

// ❌ does not compile
fn main() {
    println!("{:?}", "42".parse().unwrap());
}
error[E0282]: type annotations needed
 --> src/main.rs:3:27
  |
3 |     println!("{:?}", "42".parse().unwrap());
  |                           ^^^^^ cannot infer type of the type parameter `F` declared on the method `parse`
  |
help: consider specifying the generic argument
  |
3 |     println!("{:?}", "42".parse::<F>().unwrap());
  |                                +++++

Similarly, chaining .into() into a generic trait method without fixing the intermediate type causes error E0283 ("cannot satisfy trait bound / type annotations needed"). Supplying either a turbofish ("42".parse::<i32>()) or a typed let binding resolves the ambiguity.