Type Annotations
A type annotation explicitly states the data type of a variable, function parameter, return value, constant, or generic turbofish parameter. While Rust's type inference engine can deduce the types of most local variables inside a function body, explicit annotations are required wherever a declaration defines a boundary (such as function signatures and constants) or whenever multiple types could satisfy an expression.
Variable Type Annotation Syntax
In a let binding, a type annotation is written by placing a colon (:) and the type name immediately after the variable name (or destructuring pattern):
let mut count: u32 = 0;
─── ───── ─── ─
│ │ │ └── Initializer expression
│ │ └─────── Explicit type annotation (`: u32`)
│ └───────────── Variable identifier
└────────────────── Optional mutability modifier
fn main() {
let port: u16 = 8080;
let ratio: f32 = 0.75;
let label: &str = "production";
let coordinates: (i64, i64) = (-12, 45);
let bytes: [u8; 4] = [127, 0, 0, 1];
println!("{label} on port {port} ({ratio}): {coordinates:?}, {bytes:?}");
}
production on port 8080 (0.75): (-12, 45), [127, 0, 0, 1]
When destructuring a tuple or struct with let, the type annotation applies to the entire pattern on the left-hand side of =, not to individual identifiers inside the pattern:
fn main() {
let (host, port): (&str, u16) = ("localhost", 3000);
println!("{host}:{port}");
}
localhost:3000
Writing let (host: &str, port: u16) = ... inside a let tuple pattern is a syntax error; annotate the whole tuple let (host, port): (&str, u16) = ... instead.
Where Type Annotations Are Required vs Optional
| Construct | Type Annotation Status | Example |
|---|---|---|
const item |
Always required | const MAX_RETRIES: u32 = 3; |
static item |
Always required | static APP_NAME: &str = "cli"; |
| Function parameter | Always required | fn scale(value: f64, factor: f64) |
Function return type (non-()) |
Always required | fn area(r: f64) -> f64 |
struct fields and enum payloads |
Always required | struct User { id: u64 } |
Local let variable |
Optional (unless ambiguous) | let x = 5; or let x: i64 = 5; |
| Closure parameter and return type | Optional (usually inferred from context) | |x| x + 1 or |x: i32| -> i32 { x + 1 } |
Resolving Ambiguous Return Types (E0284 / E0282)
Some standard library functions are return-type polymorphic—meaning their behavior depends on the type expected by the caller. The two most common examples are str::parse (which can parse a string into any type implementing FromStr, such as i32, u64, f64, or IpAddr) and Iterator::collect (which can build a Vec<T>, HashSet<T>, String, or BTreeMap<K, V>).
If you call .parse() or .collect() without telling the compiler which target type you want, compilation fails with E0284 or E0282 ("type annotations needed"):
// ❌ does not compile
fn main() {
let number = "42".parse().unwrap();
println!("{number}");
}
error[E0284]: type annotations needed
--> src/main.rs:2:9
|
2 | let number = "42".parse().unwrap();
| ^^^^^^ ----- type must be known at this point
|
= note: cannot satisfy `<_ as FromStr>::Err == _`
help: consider giving `number` an explicit type
|
2 | let number: /* Type */ = "42".parse().unwrap();
| ++++++++++++
error: aborting due to 1 previous error
You can supply the required type in two ways:
- Annotating the
letbinding:let number: u32 = "42".parse().unwrap(); - Using turbofish syntax (
::<Type>) on the method call:let number = "42".parse::<u32>().unwrap();
fn main() {
// 1. Type annotation on the variable binding
let parsed_a: u32 = "42".parse().unwrap();
// 2. Turbofish `::<Type>` on the method call
let parsed_b = "42".parse::<u32>().unwrap();
let squares: Vec<i32> = (1..=4).map(|n| n * n).collect();
println!("parsed_a = {parsed_a}, parsed_b = {parsed_b}, squares = {squares:?}");
}
parsed_a = 42, parsed_b = 42, squares = [1, 4, 9, 16]
Partial Type Annotations with Type Placeholders (_)
Inside a type annotation, the underscore _ acts as a type placeholder (or inferred type wildcard). It instructs the compiler to enforce the outer container type while inferring the inner element type automatically:
fn main() {
// Enforce that the collection is a `Vec`, but let `rustc` infer the element type (`i32`)
let numbers: Vec<_> = (1..=5).collect();
// Enforce a 4-element array of `u64`
let buffer: [u64; _] = [10, 20, 30, 40];
// Parse a string while letting the error type in `Result<u16, _>` be inferred
let port_result: Result<u16, _> = "8080".parse();
println!("numbers: {numbers:?}");
println!("buffer: {buffer:?}");
println!("port: {port_result:?}");
}
numbers: [1, 2, 3, 4, 5]
buffer: [10, 20, 30, 40]
port: Ok(8080)
Note: The type placeholder
_is allowed only inside function bodies (such asletannotations and turbofish arguments). It is not allowed in function signatures, struct definitions, orconst/staticdeclarations (error[E0121]: the placeholder '_' is not allowed within types on item signatures).
Numeric Literal Suffixes vs Variable Annotations
For numeric primitives, a type annotation on the variable (let x: u64 = 100;) and a type suffix on the literal (let x = 100_u64;) achieve the same result:
fn main() {
let annotated: i16 = -300;
let suffixed = -300_i16;
assert_eq!(annotated, suffixed);
println!("Both are i16 with value {annotated}");
}
Both are i16 with value -300
Common Errors
Type Mismatch Between Annotation and Initializer (E0308)
When a type annotation is present, the compiler checks the initializer expression against that exact type and rejects any mismatch without performing implicit conversions:
// ❌ does not compile
fn main() {
let count: u32 = -5;
let message: String = "literal str slice";
}
error[E0600]: cannot apply unary operator `-` to type `u32`
--> src/main.rs:2:22
|
2 | let count: u32 = -5;
| ^^ cannot apply unary operator `-`
|
= note: unsigned values cannot be negated
error[E0308]: mismatched types
--> src/main.rs:3:27
|
3 | let message: String = "literal str slice";
| ------ ^^^^^^^^^^^^^^^^^^^ expected `String`, found `&str`
| |
| expected due to this
|
help: try using a conversion method
|
3 | let message: String = "literal str slice".to_string();
| ++++++++++++
error: aborting due to 2 previous errors