🎓 A1Learner
🔍

Rust Diverging Functions

A diverging function is a function that never hands control back. Rust writes its return type as !, the never type.

Write -> ! for never returns

A function that never hands control back ends in a panic, an endless loop, or a process exit. The arrow in its signature says so:

fn fail(message: &str) -> ! {
    panic!("{message}")
}

! fits any type

A call to fail never produces a value. So the compiler lets it stand anywhere. It can stand wherever a value is owed. Here, a &str is owed in one match arm:

let text = String::from("warm");
let label: &str = match text.as_str() {
    "warm" => "t-shirt weather",
    _ => fail("unknown weather"),
};
println!("label = {label}");

continue is a ! expression

continue never produces a value either. That is why it can sit in a match arm that owes a u32:

let mut total = 0;
for i in 0..6 {
    let addition: u32 = match i % 2 == 1 { true => i, false => continue };
    total += addition;
}
println!("total = {total}");

The unit type is the opposite

() — the unit type — has exactly one value, also written (). ! sits at the other extreme: it has no values at all.

let nothing: () = ();
println!("unit = {nothing:?}");

exit is typed ! too

std::process::exit stops the program, so it never returns. Its type is a function pointer ending in !:

let code: fn(i32) -> ! = std::process::exit;
// exit stops the program, so control never comes back

Example

fn main() {
    fn sum_odd_numbers(up_to: u32) -> u32 {
        let mut acc = 0;
        for i in 0..up_to {
            let addition: u32 = match i % 2 == 1 {
                true => i,
                false => continue,
            };
            acc += addition;
        }
        acc
    }
    println!("sum of odds below 10 = {}", sum_odd_numbers(10));
    let value: u32 = match Some(7) {
        Some(n) => n,
        None => panic!("no value"),
    };
    println!("value = {value}");
}

Key points

  • Declaring your own -> ! function is rare: you meet ! far more often inside the expressions above than in your own signatures

In this example, sum_odd_numbers adds the odd numbers below 10. Inside the match, the true arm gives i, a u32. The false arm gives continue — which never produces a value at all, so it is type !, and ! fits the u32 the match must produce. The total prints as 25. Below, the second match gives 7 from its Some arm, while its None arm calls panic!, another ! expression, so value is a plain u32 and prints as 7. Neither diverging arm runs in this program — but both had to type-check, and ! is what lets them.

Note

Code after a diverging call still type-checks

The compiler treats it as unreachable

It does not treat it as wrong

It knows control never arrives there

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

Why can continue sit in a match arm that must produce a u32?