Skip to content
Select theme

Why do both branches of an if need the same type?

Short answer: because if is an expression. if cond { a } else { b } produces one value, and that value has one type, so a and b have to agree. Nothing needs to consume the value for the rule to apply — the compiler types both arms either way.

In src/main.rs:

cargo run prints:

positive

if n > 0 { "positive" } else { "negative" } is a single expression of type &str. It isn’t a statement that runs one of two blocks and then moves on, the way an if is in C — it’s a value computed down one of two paths. let binds whatever comes back, so the right-hand side has to have exactly one type.

cargo build prints:

error[E0308]: `if` and `else` have incompatible types
--> src/main.rs:3:44
|
3 | let pass = if score >= 60 { 1 } else { "fail" };
| - ^^^^^^ expected integer, found `&str`
| |
| expected because of this
For more information about this error, try `rustc --explain E0308`.

Read the two marks: the caret is under the arm that broke the rule, the dash under the arm that set it. Here 1 made the expression an i32, and "fail" is a &str. Either arm can change — 1 and 0 work, "pass" and "fail" work — but they have to end up the same type, because let is binding one value.

The obvious objection is that nobody asked for a value: the if was only there to choose which code runs. So take the binding away.

Same error, same wording, same two marks; only the column moves. The semicolon drops the value after the expression has been typed, so the arms had to agree before anything got dropped. There’s no exemption for an if whose value nobody wants.

An arm that never produces a value fits beside any type. That’s what panic! returns — the never type, !:

cargo run prints:

4

panic!("negative: {n}") has type !, which coerces to whatever the surrounding expression needs, so this if is an i32 and sign type-checks. return, break and continue work the same way. That’s how you write a branch that fails instead of returning a number, without inventing a dummy number for it.

If the expression has one type, a path through it that yields nothing can’t supply one. Drop the else and the skipped case has only () to offer — so when a value is expected, the compiler reports it:

error[E0317]: `if` may be missing an `else` clause
--> src/main.rs:3:13
|
3 | let x = if n > 0 { 1 };
| ^^^^^^^^^^^-^^
| | |
| | found here
| expected integer, found `()`
|
= note: `if` expressions without `else` evaluate to `()`
= help: consider adding an `else` block that evaluates to the expected type
For more information about this error, try `rustc --explain E0317`.

Different code, same cause. Both errors above are the compiler saying the same thing: an if is one expression, so every path through it has to produce a value of the one type it has.