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
- Argument-driven inference: Passing
42_u16toVec::push(&mut self, value: T)constrainsTtou16. - Usage-driven inference: Calling
Vec::new()creates aVec<?T>whose element type remains unknown until a subsequentvec.push("hello")unifies?Twith&str. - 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.