Type Inference
Type inference in Rust (based on a bidirectional Hindley–Milner constraint solver) automatically deduces the concrete types of local variables, generic type arguments, and closure parameters from how values are initialized, passed, and returned within a function body.
Boundary Rule: Explicit Signatures vs. Inferred Bodies
Rust draws a deliberate boundary between function/item signatures and function bodies:
- Function, method,
const, andstaticsignatures require explicit type annotations. This guarantees that changing a function's body never silently breaks downstream callers, enables modular per-function type checking, and makes API documentation self-contained. - Inside a function body,
letbindings, closures, and generic calls infer their types forward from initializers and backward from how the variable is used later in the function!
fn parse_and_collect(raw: &str) -> Vec<u32> <── Explicit types at the function boundary
{
let mut out = Vec::new(); <── Inferred as `Vec<u32>` from return type!
for token in raw.split(',') { <── `token` inferred as `&str`
if let Ok(n) = token.trim().parse() { <── `parse::<u32>()` inferred from `out.push(n)`!
out.push(n);
}
}
out
}
fn parse_and_collect(raw: &str) -> Vec<u32> {
let mut out = Vec::new();
for token in raw.split(',') {
if let Ok(n) = token.trim().parse() {
out.push(n);
}
}
out
}
fn main() {
let values = parse_and_collect("10, 25, invalid, 40");
println!("Parsed values: {values:?}");
}
Parsed values: [10, 25, 40]
Partial Type Hints with the _ Type Placeholder and Turbofish (::<T>)
When the compiler cannot uniquely determine a type from context (E0282), you do not need to spell out the entire nested type. Use either:
- The
_type placeholder (let v: Vec<_> = ...;) to specify the outer container while letting the compiler infer the inner element type. - Turbofish syntax (
method::<Type>()) on a generic function or method call (iter.collect::<Vec<_>>(),"42".parse::<i64>()).
use std::collections::BTreeMap;
fn main() {
let pairs = [("http", 80_u16), ("https", 443)];
// Specifies the container `BTreeMap` while `_` infers `&str` and `u16`:
let ports: BTreeMap<_, _> = pairs.into_iter().collect();
println!("Ports: {ports:?}");
}
Ports: {"http": 80, "https": 443}
Default Numeric Fallback (i32 and f64)
If an integer literal (let x = 42;) or floating-point literal (let y = 3.14;) is unconstrained by any surrounding type expectation, the type checker defaults unconstrained integer inference variables to i32 and unconstrained floating-point inference variables to f64.