Type Inference
In this page:
let name = value; // type inferred
let name: Type = expr.parse().unwrap();
Inference from Literals
When you write let x = 5;, Rust looks at the literal and defaults it to i32 without you writing the type. The compiler performs this inference at compile time -- there is no runtime type-checking involved.
Example: Inference from Literals
// Entry point: execution of the program starts here
fn main() {
// Declare an immutable variable `count`, set to `5`
let count = 5;
// Declare an immutable variable `name`, set to `"Rustacean"`
let name = "Rustacean";
// Print "{} appears {} times" to the console, substituting in `name, count` (with a trailing newline)
println!("{} appears {} times", name, count);
}
Login to try C/C++/Java/PHP code in the editor
Inference from Later Usage
Rust can also infer a variable's type from how it is used later in the code, not just from its initial value, as long as the usage is unambiguous.
Vec<i32> only after seeing the first push(1) call.Example: Inference from Later Usage
// Entry point: execution of the program starts here
fn main() {
// Declare a mutable variable `numbers`, initialized to `Vec::new()`
let mut numbers = Vec::new();
// Append `1` to `numbers`
numbers.push(1);
// Append `2` to `numbers`
numbers.push(2);
// Print "{:?}" to the console, substituting in `numbers` (with a trailing newline)
println!("{:?}", numbers);
}
Login to try C/C++/Java/PHP code in the editor
Function Signatures Need Explicit Types
Unlike local variables, function parameters and return types are never inferred -- they must always be written out explicitly. This makes function signatures a reliable, self-documenting contract.
Example: Function Signatures Need Explicit Types
// Define the function `double` taking `n` and returning an integer
fn double(n: i32) -> i32 {
n * 2
}
// Entry point: execution of the program starts here
fn main() {
// Print "Doubled: {}" to the console, substituting in `double(21)` (with a trailing newline)
println!("Doubled: {}", double(21));
}
Login to try C/C++/Java/PHP code in the editor
When Inference Fails
Sometimes the compiler cannot determine a type on its own, such as when a generic parsing method could produce several different types. In that case you must supply an explicit annotation to resolve the ambiguity.
Example: When Inference Fails
// Entry point: execution of the program starts here
fn main() {
// Declare an immutable variable `parsed` (an integer), set to `"42".parse().unwrap()`
let parsed: i32 = "42".parse().unwrap();
// Print "Parsed as i32: {}" to the console, substituting in `parsed` (with a trailing newline)
println!("Parsed as i32: {}", parsed);
}
Login to try C/C++/Java/PHP code in the editor
- Believing Rust is dynamically typed because you rarely write type annotations -- every value still has one fixed type, just inferred.
- Leaving out a type annotation in a case where the compiler genuinely cannot infer it, leading to a compile error.
- Assuming inference works across function boundaries -- function parameters and return types must always be explicitly annotated.
- Rust infers types from context, such as literal values, later usage, or return values.
- Every value still has exactly one concrete type at compile time -- inference does not make Rust dynamically typed.
- Function signatures always require explicit parameter and return types, even though local variables often don't.
- When inference is ambiguous, the compiler reports an error asking for an explicit annotation.
Chapter Quiz — Complete all 7 topics to unlock
0/7 topics done
Complete these topics first: