What is the point of diverging functions in Rust?

Solution 1:

The main difference between these signatures boils down to the fact that ! can coerce into any other type, and thus is compatible with any other type (since this code path is never taken, we can assume it to be of any type we need). It's important when we have multiple possible code paths, such as if-else or match.

For example, consider the following (probably contrived, but hopefully clear enough) code:

fn assert_positive(v: i32) -> u32 {
    match v.try_into() {
        Ok(v) => v,
        Err(_) => func(),
    }
}

When func is declared to return !, this function compiles successfully. If we drop the return type, func will be declared as returning (), and the compilation breaks:

error[E0308]: `match` arms have incompatible types
 --> src/main.rs:8:19
  |
6 | /     match v.try_into() {
7 | |         Ok(v) => v,
  | |                  - this is found to be of type `u32`
8 | |         Err(_) => func(),
  | |                   ^^^^^^ expected `u32`, found `()`
9 | |     }
  | |_____- `match` arms have incompatible types

You can also compare this with definition for Result::unwrap:

pub fn unwrap(self) -> T {
    match self {
        Ok(t) => t,
        Err(e) => unwrap_failed("called `Result::unwrap()` on an `Err` value", &e),
    }
}

Here, unwrap_failed is returning !, so it unifies with whatever type is returned in Ok case.

Solution 2:

The compiler knows that anything that follows a diverging expression (w.r.t. evaluation order) is unreachable. It can use this information to avoid false negatives when it comes to deciding whether a local variable is initialized or not.

Consider the following example:

use rand; // 0.8.4

fn main() {
    let foo;
    if rand::random::<bool>() {
        foo = "Hello, world!";
    } else {
        diverge();
    }
    println!("{foo}");
}

fn diverge() {
    panic!("Crash!");
}

We declare a variable foo, but we only initialize it in one branch of the if expression. This fails to compile with the following error:

error[E0381]: borrow of possibly-uninitialized variable: `foo`
  --> src/main.rs:10:15
   |
10 |     println!("{foo}");
   |               ^^^^^ use of possibly-uninitialized `foo`

However, if we change the definition of our diverge function like this:

fn diverge() -> ! {
    panic!("Crash!");
}

then the code successfully compiles. The compiler knows that if the else branch is taken, it will never reach the println! because diverge() diverges. Therefore, it's not an error that the else branch doesn't initialize foo.