The Type System & Inference
Rust is statically typed: every value has a type known at compile time. The compiler infers types when possible; annotations clarify intent at API boundaries and resolve ambiguity.
Recipe
fn main() {
let x = 10; // inferred i32
let y: u64 = 10; // explicit
let items = vec![1, 2, 3]; // Vec<i32>
let mapped: Vec<_> = items.iter().map(|n| n * 2).collect();
println!("{x} {y} {:?}", mapped);
}When to reach for this: Choosing explicit types at public APIs, turbofish ::<T>, and understanding why inference sometimes needs a hint.
Working Example
use std::io::{self, BufRead};
fn read_numbers<R: BufRead>(reader: R) -> io::Result<Vec<i32>> {
let mut out = Vec::new();
for line in reader.lines() {
let n: i32 = line?.trim().parse()?;
out.push(n);
}
Ok(out)
}
fn main() -> io::Result<()> {
let stdin = io::stdin();
let nums = read_numbers(stdin.lock())?;
let sum: i32 = nums.iter().sum();
println!("sum={sum}");
Ok(())
}What this demonstrates:
- Generic
R: BufReadinferred at call site ?onparse()propagatesParseIntErrorviaFrom- Explicit
i32onparsetarget - Return type
io::Result<Vec<i32>>guides error conversions
Deep Dive
How Inference Works
- Compiler assigns types based on constraints from assignments, function signatures, and trait impls
- Integer literals default to
i32, floats tof64without other hints collect()often needs turbofish:collect::<Vec<_>>()- Type errors often point to the first place constraints conflict
Annotations vs Turbofish
let v = Vec::<String>::new();
let v: Vec<String> = Vec::new();
let nums = "1 2 3".split_whitespace().map(|s| s.parse::<u32>().unwrap()).collect::<Vec<_>>();Never Type !
fn exit(code: i32) -> ! {
std::process::exit(code);
}Expressions of type ! unify with any type in match arms (diverging).
Gotchas
- Ambiguous
collect()-let v = iter.collect();errors. Fix:collect::<Vec<_>>()or annotatelet v: Vec<T>. - Numeric literal ambiguity in generics -
let x: T = 1;may fail. Fix: UseT::from(1)or literal suffix. - Over-annotating locals - Noise without benefit. Fix: Annotate public items and where inference fails.
- Assuming
usize==u64- Platform-dependentusize. Fix: Cast explicitly for FFI/serialization. - Shadowing changes inferred type -
let x = 1; let x = "a";changes type silently. Fix: Use distinct names in long scopes.
Alternatives
| Alternative | Use When | Don't Use When |
|---|---|---|
Turbofish ::<T> | One-off method type param | Repeated - use type alias |
Type alias type Id = u64 | Domain clarity | Single-use primitives |
impl Trait in returns | Hide concrete type | Callers need named type |
| Newtype wrapper | Distinct units | Internal-only math |
FAQs
Is Rust dynamically typed anywhere?
No in safe application code. dyn Trait is still statically checked at compile time for trait object usage.
Can inference fail on recursive functions?
Sometimes. Annotate the return type on recursive functions explicitly.
What is the default integer type?
i32 for untyped integer literals in most contexts.
Does `let _ = expr` affect inference?
The expression is still type-checked. _ discards the binding but not the type constraints.
How do closures affect inference?
Closure param/return types are inferred from usage. Annotate with |x: u32| -> u32 when needed.
What is type ascription?
Another name for explicit annotations: let x: i32 = 1.
Can I print a type at compile time?
Not directly. Use std::any::type_name::<T>() at runtime for debugging.
Do type aliases create new types?
No. type Meters = f64 is interchangeable with f64. Use newtype struct for distinction.
How does inference interact with `async`?
Future output types are inferred from .await usage and return position in async fns.
What tools help beyond the compiler?
rust-analyzer shows inferred types on hover. cargo check is the source of truth.
Related
- Scalar & Compound Types - primitive types
- Generics - parametric types
- Trait Bounds - constraining generics
- Functions & Expressions - return type expressions
Stack versions: This page was written for Rust 1.97.0 (edition 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, and Polars 0.46+.