Skip to content
rusthacks.dev
Generics

Rust Error E0308 Mismatched Types With Practical Fixes

E0308 mismatched types means the compiler expected one type and found another. Learn the five shapes it takes, from a missing semicolon to if branches that differ.

7 min read
Summarize in AI
Flat illustration for the post “Rust Error E0308 Mismatched Types With Practical Fixes”, composed around generics.

error[E0308]: mismatched types means the compiler expected one type at a particular spot and found another. It is the error Rust programmers see most, because Rust has no implicit conversions between most types: an i32 is not a usize, a &str is not a String, a value is not an Option of that value. Each of those gaps is a decision, and the message tells you the two types that clashed and, usually, the one-line fix. This post covers the six shapes of mismatch you will meet, each with the compiler's real output from Rust 1.99, and the repair. The key skill is reading the two phrases "expected X" and "found Y" and asking which side is the one you meant.

Reading the message

Every E0308 has an expected ... found ... pair. The label on the underlined code says what the compiler found there. A second label, on another line, says why it expected something different: the function signature, the declared variable type, the other branch of an if. The help lines propose a conversion when a standard one exists. Read the arrow in the form: "this spot wanted X because of that line, and you gave Y".

Shape one: a trailing semicolon

The oddest message for newcomers is expected i32, found ():

src/main.rsrust
fn double(n: i32) -> i32 {
    n * 2;
}
Text
error[E0308]: mismatched types
 --> a308.rs:1:22
  |
1 | fn double(n: i32) -> i32 {
  |    ------            ^^^ expected `i32`, found `()`
  |    |
  |    implicitly returns `()` as its body has no tail or `return` expression
2 |     n * 2;
  |          - help: remove this semicolon to return this value

In Rust, a block's value is its last expression, and a semicolon turns an expression into a statement whose value is (). The semicolon after n * 2 threw the result away, so the function returned () where it promised an i32. The compiler spots it and says exactly which semicolon to remove. This error also appears at the end of an if branch or a match arm when one arm ends with a semicolon and another does not.

Shape two: &str where String is expected

src/main.rsrust
fn greet(name: String) -> String {
    format!("hello {name}")
}

fn main() {
    println!("{}", greet("ferris"));
}
Text
error[E0308]: mismatched types
 --> b308.rs:6:26
  |
6 |     println!("{}", greet("ferris"));
  |                    ----- ^^^^^^^^ expected `String`, found `&str`
  |                    |
  |                    arguments to this function are incorrect
  |
help: try using a conversion method
  |
6 |     println!("{}", greet("ferris".to_string()));
  |                                  ++++++++++++

A string literal is a &str, a borrowed slice, and String is the owned, growable type. The suggestion .to_string() works, and it allocates. The better fix is usually in the signature: if greet only reads the name, take &str, and then callers can pass a literal or a &String with no conversion at all. Change the parameter when the function does not need ownership; convert at the call when it genuinely stores the string.

Shape three: the two branches disagree

An if is an expression, so both arms must produce the same type:

src/main.rsrust
let ready = true;
let label = if ready { "go" } else { 0 };
Text
error[E0308]: `if` and `else` have incompatible types
 --> c308.rs:3:42
  |
3 |     let label = if ready { "go" } else { 0 };
  |                            ----          ^ expected `&str`, found integer
  |                            |
  |                            expected because of this

The first branch fixed the type as &str, so the second must match. The same applies to every arm of a match. The fix is to make the arms agree, here "stop" instead of 0. When the arms truly produce different things, unify them in a type that can hold both, an enum or a trait object, or return from the function inside one branch so it does not need a value.

Shape four: a plain value where an Option or Result belongs

src/main.rsrust
fn find(items: &[i32], target: i32) -> Option<usize> {
    for (i, item) in items.iter().enumerate() {
        if *item == target {
            return i;
        }
    }
    None
}
Text
error[E0308]: mismatched types
 --> e308.rs:4:20
  |
1 | fn find(items: &[i32], target: i32) -> Option<usize> {
  |                                        ------------- expected `Option<usize>` because of return type
...
4 |             return i;
  |                    ^ expected `Option<usize>`, found `usize`
  |
help: try wrapping the expression in `Some`
  |
4 |             return Some(i);
  |                    +++++ +

Rust does not wrap values automatically. The function's return type says Option<usize>, so the found value needs Some(...), and the failing path already returns None. The same thing happens with Ok(...) for Result. When the compiler offers to wrap, accept it; this is almost always the intended fix.

Shape five: a literal of the wrong type

src/main.rsrust
let count: u32 = "42";
Text
error[E0308]: mismatched types
 --> f308.rs:2:22
  |
2 |     let count: u32 = "42";
  |                ---   ^^^^ expected `u32`, found `&str`
  |                |
  |                expected due to this

Rust never parses a string into a number for you. Call parse and handle the possibility of failure: "42".parse().expect("not a number") for a quick script, or match the Result in real code. The annotation on count tells parse which type to produce.

Shape six: arithmetic across number types

Mixing numeric types in arithmetic is the form that most often shows up as a different error, because the operators are traits:

src/main.rsrust
fn average(values: &[f64]) -> f64 {
    let total: f64 = values.iter().sum();
    total / values.len()
}
Text
error[E0277]: cannot divide `f64` by `usize`
 --> d308.rs:3:11
  |
3 |     total / values.len()
  |           ^ no implementation for `f64 / usize`
  |
  = help: the trait `Div<usize>` is not implemented for `f64`

len() returns usize and total is f64, and Rust refuses to pick a conversion because a usize can be too large to represent exactly as an f64, and an f64 can be negative or fractional. State the conversion yourself with as f64, which is a deliberate and visible choice. For narrowing conversions use try_from, which reports overflow instead of truncating silently. All the repaired forms run in one program:

src/main.rsrust
fn double(n: i32) -> i32 {
    n * 2 // no semicolon: this is the value the function returns
}

fn greet(name: &str) -> String {
    format!("hello {name}")
}

fn average(values: &[f64]) -> f64 {
    let total: f64 = values.iter().sum();
    total / values.len() as f64
}

fn find(items: &[i32], target: i32) -> Option<usize> {
    for (i, item) in items.iter().enumerate() {
        if *item == target {
            return Some(i);
        }
    }
    None
}

fn main() {
    println!("{}", double(4));
    println!("{}", greet("ferris"));
    println!("{}", average(&[1.0, 2.0, 4.0]));
    println!("{:?} {:?}", find(&[5, 6, 7], 6), find(&[5, 6, 7], 9));

    let ready = true;
    let label = if ready { "go" } else { "stop" };
    println!("{label}");

    let count: u32 = "42".parse().expect("not a number");
    println!("{}", count + 1);

    // Converting between integer types is explicit, and fallible when the target is narrower.
    let big: i64 = 300;
    println!("{:?} {:?}", u8::try_from(big), u16::try_from(big));
}
Text
8
hello ferris
2.3333333333333335
Some(1) None
go
43
Err(TryFromIntError(PosOverflow)) Ok(300)

The last line shows why try_from is the safe choice: converting 300 to u8 returned an error with PosOverflow, while u16 accepted it. An as u8 cast would have wrapped 300 to 44 and kept going.

When inference is the cause

Sometimes the mismatch is reported far from the code you got wrong, because type inference made a decision earlier. A bare integer literal defaults to i32 unless something later pins it, so let mut total = 0; followed by total += item_count where item_count is a u64 works, while the same line with total used first as an i32 argument reports the clash at the second use. The error is on the line where two inferred constraints collide, and the real mistake is the earlier one that started the chain. Add an explicit annotation to the first variable (let mut total: u64 = 0;) and rebuild. If the error moves to a different line, you have found where the types really disagree. This trick of annotating to localize an error works for most inference-related type errors, and it is a good first move whenever the line the compiler complains about looks correct.

A checklist for mismatches

  • Is there a semicolon on the last line of a block that should produce a value?
  • Is one side an owned type (String, Vec<T>, Box<T>) and the other a borrow (&str, &[T], &T)? Pick which one the function should take.
  • Does the value need wrapping in Some, Ok or Err, or unwrapping with ??
  • Do the arms of the if or match all produce the same type?
  • Are two integer or float types mixed, and have you chosen how to convert?
  • Did type inference decide something earlier than you expected? Add an annotation to the first let and the real clash will move to where the inference went wrong.

For the neighboring errors, E0277 appears when a type lacks a trait rather than being the wrong type, and E0282 appears when the compiler cannot decide a type at all.

Also inRust
Taggede0308
Share

Keep reading

Get new posts by email

We send one email when a new post is published. No spam.

Unsubscribe in one click. No spam, ever.