Skip to content

Rust Error E0308: Mismatched Types

Error E0308 means Rust expected a value of one type, but you gave it a value of a different type. It’s probably the error you’ll see most often in Rust, and the message almost always tells you both types, so it’s usually quick to fix. (Looking for a different error? See the compiler errors overview.)

error[E0308]: mismatched types
--> src/main.rs:6:29
|
6 | let user = User { name: "Ferris" };
| ^^^^^^^^ expected `String`, found `&str`
|
help: try using a conversion method
|
6 | let user = User { name: "Ferris".to_string() };
| ++++++++++++

Here is the program that produces it:

struct User {
name: String,
}
fn main() {
let user = User { name: "Ferris" };
println!("{}", user.name);
}

Every E0308 message has the same shape: expected A, found B.

  • Expected is the type Rust needs at that spot. It comes from somewhere else: a type annotation, a function’s parameter or return type, a struct field, or another branch of an if/match.
  • Found is the type of the value you actually wrote there.

In the example, the name field was declared as String, so that’s what Rust expects. But "Ferris" is a string literal, which has the type &str. They are related, but they are not the same type.

Look for the help: lines too. For E0308 the compiler often suggests the exact fix, like .to_string() above.

Rust never converts between types silently. In some languages a number quietly becomes a string, or a big number is quietly cut down to fit a smaller one. Those hidden conversions cause real bugs: lost data, wrong results, crashes far away from the actual mistake.

So Rust makes every conversion visible in the code. It’s a bit more typing, but when you read the code later you can see exactly where a value changes type and whether that step can fail. See Data Types for the types themselves.

Each fix below starts with the shape of the error, so you can jump to the one that matches yours.

error[E0308]: mismatched types
--> src/main.rs:1:46
|
1 | fn total_price(price: u32, quantity: u32) -> u32 {
| ----------- ^^^ expected `u32`, found `()`
| |
| implicitly returns `()` as its body has no tail or `return` expression
2 | price * quantity;
| - help: remove this semicolon to return this value

If you see found (), check the last line of the function. In Rust, the last expression of a block without a semicolon is its return value. Adding ; turns it into a statement, so the function returns () (the “nothing” type) instead. Delete the semicolon:

fn total_price(price: u32, quantity: u32) -> u32 {
price * quantity // no semicolon: this value is returned
}
fn main() {
println!("Total: {}", total_price(250, 3));
}

Output:

Total: 750
▶ Try it Yourself

The same thing happens inside if blocks and match arms. More in Functions.

String is an owned, growable string. &str is a borrowed view of some text. See Strings for the full story. Converting is short in both directions:

  • &str to String: .to_string() or String::from(...).
  • String to &str: borrow it with & (or call .as_str()).

The second case shows up as expected &str, found String, usually when you pass a String to a function that takes &str.

struct User {
name: String,
}
fn greet(name: &str) {
println!("Hello, {name}!");
}
fn main() {
// &str -> String: .to_string() or String::from()
let user = User { name: "Ferris".to_string() };
// String -> &str: borrow it with & (or call .as_str())
greet(&user.name);
greet(user.name.as_str());
let mut tags: Vec<String> = Vec::new();
tags.push(String::from("rust"));
println!("{tags:?}");
}

Output:

Hello, Ferris!
Hello, Ferris!
["rust"]
▶ Try it Yourself
error[E0308]: mismatched types
--> src/main.rs:3:22
|
3 | let count: i32 = names.len();
| --- ^^^^^^^^^^^ expected `i32`, found `usize`
| |
| expected due to this

i32, i64, u32 and usize are all different types, and Rust won’t mix them. Lengths and indexes are always usize. Pick a conversion:

  • .into() (or i64::from(x)) when the conversion can never lose data, like i32 to i64.
  • i64::try_from(x) (or .try_into()) when it might not fit. It returns a Result, so you decide what happens on failure.
  • as is the quick cast. It never fails, but it silently cuts off values that don’t fit, so use it only when you know the value is small.
fn average(total: i64, count: i64) -> i64 {
total / count
}
fn main() {
let scores: Vec<i32> = vec![80, 95, 70];
let total: i32 = scores.iter().sum();
// i32 -> i64 always fits, so .into() is enough
let total: i64 = total.into();
// usize -> i64 might not fit, so try_from asks first
let count = i64::try_from(scores.len()).unwrap();
println!("Average: {}", average(total, count));
// `as` also works, but it silently cuts off values that don't fit
let players = scores.len() as i32;
println!("{players} players");
}

Output:

Average: 81
3 players
▶ Try it Yourself

Often the simplest fix is to change the annotation instead: let count: usize = names.len();. The Type Casting and Conversions pages go deeper.

error[E0308]: mismatched types
--> src/main.rs:3:27
|
3 | let position: usize = names.iter().position(|n| *n == "Ben");
| ----- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected `usize`, found `Option<usize>`
| |
| expected due to this

Option<usize> is not a usize. It’s “maybe a usize, maybe nothing”. This mismatch goes both ways:

  • Expected T, found Option<T>: you have to deal with the “nothing” case, using match, if let, or unwrap_or(default).
  • Expected Option<T>, found T: your function returns Option, so wrap the value: Some(10) instead of 10.
fn find_discount(code: &str) -> Option<u32> {
if code == "SAVE10" {
return Some(10); // T -> Option<T>: wrap it in Some
}
None
}
fn main() {
let names = vec!["Ana", "Ben", "Cy"];
// Option<T> -> T: handle the "nothing found" case...
match names.iter().position(|n| *n == "Ben") {
Some(i) => println!("Ben is at {i}"),
None => println!("Ben isn't here"),
}
// ...or choose a default value with unwrap_or
let discount: u32 = find_discount("HELLO").unwrap_or(0);
println!("Discount: {discount}%");
}

Output:

Ben is at 1
Discount: 0%
▶ Try it Yourself

The compiler may suggest .expect("REASON"). That works too, but it crashes the program when the value is None, so only use it when None really can’t happen. See Option.

Fix 5: Make every if/else and match branch the same type

Section titled “Fix 5: Make every if/else and match branch the same type”
error[E0308]: `if` and `else` have incompatible types
--> src/main.rs:3:52
|
3 | let label = if stock > 0 { "in stock" } else { stock };
| ---------- ^^^^^ expected `&str`, found integer
| |
| expected because of this

When if or match produces a value, Rust needs to know its one type. So every branch must produce the same type. The first branch sets the expectation, and the error points at the branch that disagrees. For match the message says “match arms have incompatible types”. Make the odd branch agree:

fn status_text(code: u16) -> String {
match code {
200 => "OK".to_string(),
404 => "Not Found".to_string(),
_ => format!("Unknown code {code}"), // every arm is a String
}
}
fn main() {
let stock = 0;
let label = if stock > 0 { "in stock" } else { "sold out" }; // both &str
println!("{label}");
println!("{}", status_text(404));
println!("{}", status_text(418));
}

Output:

sold out
Not Found
Unknown code 418
▶ Try it Yourself

Here one arm needed format!, which makes a String, so the other arms became String too. See if/else and match.

  • Reading user input. let age: u32 = input.trim(); gives “expected u32, found &str”. Text is not a number. Fix: parse it: let age: u32 = input.trim().parse().unwrap(); (or handle the error, see Result).
  • Pushing literals into a Vec<String>. tags.push("rust") gives “expected String, found &str”. Fix: tags.push("rust".to_string()), or make it a Vec<&str> if the strings are all literals.
  • Math with mixed number types. total + n where total is i64 and n is i32 gives E0308 (and often E0277 next to it). Fix: convert one side first: total + i64::from(n).

Quick check

1. A function says -> i32, but the error says "expected i32, found ()". What's the most likely cause?

2. A function takes &str and you have let name = String::from("Ana");. How do you pass it?

3. Which conversion can never silently lose data?

Score: 0 / 3