The Error
You run cargo build and the compiler stops you cold:
error[E0308]: mismatched types
--> src/main.rs:5:18
|
5 | let x: i32 = "hello";
| --- ^^^^^^^ expected `i32`, found `&str`
|
= note: expected type `i32`
found type `&str`
Build fails. Rust won't compile code with a type mismatch โ no silent coercion, no implicit casting, no "close enough." The variable x was declared as i32 but assigned a string literal. In Rust, that's a hard stop.
Why This Happens
Rust is statically typed: every variable gets exactly one type at compile time. Assign the wrong type and the compiler rejects it outright. No exceptions.
This actually catches real bugs early. A silent coercion in C might truncate a 64-bit value to 32 bits without warning; Rust forces you to handle it explicitly. The tradeoff is that you can't be sloppy about types โ which is the point.
Common triggers:
- Annotating a variable with the wrong type for the value being assigned
- Returning the wrong type from a function
- Passing a
&strwhere aStringis expected (or vice versa) - Mixing integer types like
i32vsu32vsusize - Using an
Option<T>value where a bareTis expected
Quick Fixes
Fix 1: Correct the type annotation
If the value is right but the annotation is wrong, fix the annotation:
// Before (wrong annotation)
let x: i32 = "hello"; // error: expected i32, found &str
// After (annotation matches value)
let x: &str = "hello"; // OK
// or just let Rust figure it out
let x = "hello"; // inferred as &str
Fix 2: Correct the value
If the type annotation is intentional, assign the right value:
// You wanted an integer, not a string
let x: i32 = 42; // OK
Fix 3: Convert explicitly
When you genuinely need to change types, Rust requires an explicit conversion. Pick the one that fits your case:
// &str โ String
let s: String = "hello".to_string();
let s: String = String::from("hello");
// String โ &str
let owned = String::from("hello");
let borrowed: &str = &owned;
// i64 โ i32 (truncates if value exceeds 2_147_483_647)
let big: i64 = 100;
let small: i32 = big as i32;
// String โ integer
let n: i32 = "42".parse().unwrap();
// safer โ explains what failed:
let n: i32 = "42".parse().expect("not a valid number");
Common Scenarios
Function return type mismatch
A trailing semicolon silently changes the return type to (). Remove it:
// error: expected i32, found ()
fn get_value() -> i32 {
let x = 42;
// forgot to return x โ or added a semicolon after it
}
// fix: drop the semicolon on the last expression
fn get_value() -> i32 {
let x = 42;
x // implicit return โ no semicolon
}
Mixing integer types
Vec::len() returns usize, not i32. This trips up a lot of beginners:
fn print_length(v: &Vec<i32>) {
let len: i32 = v.len(); // error: expected i32, found usize
}
// fix: use the correct type or cast explicitly
fn print_length(v: &Vec<i32>) {
let len: usize = v.len(); // correct type
let len2: i32 = v.len() as i32; // explicit cast (safe for small vecs)
}
Option unwrapping
Iterator::next() returns Option<T>, not T directly. Either propagate the Option or provide a fallback:
fn first_char(s: &str) -> char {
s.chars().next() // error: expected char, found Option<char>
}
// option 1: change the return type to propagate the Option
fn first_char(s: &str) -> Option<char> {
s.chars().next()
}
// option 2: unwrap with a default for empty strings
fn first_char(s: &str) -> char {
s.chars().next().unwrap_or('\0')
}
Struct field mismatch
struct User {
age: u32,
}
// error: expected u32, found i32
let user = User { age: -1i32 };
// fix: use a valid u32 value
let user = User { age: 25u32 };
Reading the Error Message
Rust's compiler gives you the exact location and both types involved. Here's how to read it:
error[E0308]: mismatched types
--> src/main.rs:5:18
|
5 | let x: i32 = "hello";
| --- ^^^^^^^ expected `i32`, found `&str`
| |
| expected due to this type annotation
src/main.rs:5:18โ file, line 5, column 18 (the mismatch location)---underi32โ the declared type^^^^^^^under"hello"โ the offending valueexpected X, found Yโ X is what the context demands, Y is what you gave it
Expected means the type the context required โ an annotation, function signature, or struct field. Found is the actual type of what you wrote. Fix one side to match the other.
Let Type Inference Do the Work
Not sure what type a function returns? Skip the annotation and let Rust infer it:
// Let Rust figure out the type
let x = some_function();
// Want to know the exact type? Force a deliberate error:
let _: () = some_function(); // Rust will tell you the real type
The error will read expected (), found SomeType โ revealing the actual inferred type. Quick, free type inspection with no IDE required.
Verification
Run cargo build. A clean build looks like:
Compiling myproject v0.1.0 (/path/to/myproject)
Finished dev [unoptimized + debuginfo] target(s) in 0.42s
No error[E0308] in the output means you're done. If you have tests, run them too:
cargo test

