Inference and Diverging Expressions
A diverging expression is any expression whose evaluation never completes normally and therefore has the never type !. Because a diverging expression never produces a runtime value, ! coerces implicitly to any type T during type unification—allowing return, break, continue, panic!(), and infinite loop {} expressions to sit alongside value-producing branches in match and if/else expressions.
Built-In Diverging Expressions in Rust
| Diverging Expression | Why It Has Type ! |
|---|---|
panic!(...), todo!(), unimplemented!(), unreachable!() |
Unwinds the stack (or aborts) without returning to the enclosing expression |
return expr |
Immediately exits the enclosing function |
break / break value |
Immediately exits the enclosing loop |
continue |
Immediately jumps to the next iteration of the enclosing loop |
loop { ... } (with no break) |
Runs forever without exiting |
std::process::exit(code) / std::process::abort() |
Terminates the OS process |
How ! Unifies with Concrete Types in Branching Expressions
When the type checker unifies the types of multiple arms in a match or if/else expression, any arm of type ! coerces to the type required by the remaining arms:
let port: u16 = match raw.parse::<u16>() {
Ok(num) => num, // Arm 1 has type `u16`
Err(_) => return None, // Arm 2 (`return None`) has type `!`
}; // Unification: `unify(u16, !) -> u16`!
fn parse_positive_items(inputs: &[&str]) -> Result<Vec<u32>, String> {
let mut out = Vec::new();
for &raw in inputs {
if raw.is_empty() {
// `continue` has type `!`, which coerces to `u32`!
let _skipped: u32 = continue;
}
let number: u32 = match raw.parse::<u32>() {
Ok(0) => continue, // `!` coerces to `u32`
Ok(n) => n, // `u32`
Err(e) => {
// `return Err(...)` has type `!`, which coerces to `u32`!
return Err(format!("invalid number {:?}: {}", raw, e));
}
};
out.push(number);
}
Ok(out)
}
fn main() {
println!("valid: {:?}", parse_positive_items(&["10", "", "0", "25"]));
println!("invalid: {:?}", parse_positive_items(&["10", "bad"]));
}
valid: Ok([10, 25])
invalid: Err("invalid number \"bad\": invalid digit found in string")
Diverging Expressions in let ... else Statements
The let PATTERN = EXPR else { DIVERGING_BLOCK }; statement (stabilized in Rust 1.65) relies directly on diverging type checking: the compiler enforces that the else block has type ! (it must diverge via return, break, continue, or panic!), which guarantees that all pattern bindings are initialized on every control-flow path following the statement:
// ❌ does not compile
fn extract_header(line: &str) -> &str {
let Some((key, _val)) = line.split_once(':') else {
println!("missing colon");
// Error E0308: `else` block of `let...else` MUST diverge (`!`),
// not fall through with `()`!
};
key
}
fn main() {}
error[E0308]: `let...else` Waiting for diverging block
--> src/main.rs:3:50
|
3 | let Some((key, _val)) = line.split_once(':') else {
| __________________________________________________^
4 | | println!("missing colon");
5 | | // Error E0308: `else` block of `let...else` MUST diverge (`!`),
6 | | // not fall through with `()`!
7 | | };
| |_____^ expected `!`, found `()`
|
= note: the `else` block of `let...else` must diverge (`return`, `break`, `continue`, or `panic!`)
Unreachable Code Warnings After Diverging Expressions
Because the compiler knows an expression has type !, any statements written sequentially after a diverging expression in the same block are flagged by the #[warn(unreachable_code)] lint and eliminated during dead-code elimination.