Advanced Type Features

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.