Rust Data Types

Never Type !

The never type, written !, is Rust's primitive empty type (called a bottom type in type theory). While the unit type () has exactly one possible value, the never type ! has zero possible values—it cannot be instantiated at runtime. An expression has type ! when evaluating it is guaranteed never to return normally to the surrounding expression.

Value Count Comparison: bool vs () vs !

Type Number of Possible Runtime Values Size in Memory (size_of) Meaning When Returned by a Function
bool 2 (true, false) 1 byte Function returns and hands back 1 bit of information
() (Unit type) 1 (()) 0 bytes Function returns normally, carrying no data
! (Never type) 0 (uninhabited!) 0 bytes Function never returns to its caller (diverges!)
use std::mem::size_of;

fn main() {
    println!("size_of::<bool>() = {} byte", size_of::<bool>());
    println!("size_of::<()>()   = {} bytes", size_of::<()>());
}
size_of::<bool>() = 1 byte
size_of::<()>()   = 0 bytes

Diverging Expressions That Produce !

Any expression that jumps away or halts execution before producing a value is called a diverging expression and has type !:

Diverging Expression Why It Has Type !
panic!("msg") / todo!() / unimplemented!() / unreachable!() Unwinds the call stack (or aborts the process); never yields a value to the enclosing expression
return or return expr Immediately exits the enclosing function; the expression around return never receives a value
break or continue Immediately jumps out of the current loop iteration; code surrounding break/continue never receives a value
loop { ... } (with no break) Runs forever (or until return/panic!); never falls through the closing }
std::process::exit(code) / std::hint::unreachable_unchecked() Terminates the OS process immediately

Automatic Coercion: ! Coerces into Any Type T

Because an expression of type ! can never actually finish and produce a value at runtime, the compiler allows ! to coerce automatically into any other type T!

This rule is what makes control flow inside match, if/else, and let ... else expressions work seamlessly when one branch computes a value of type T and the other branch diverges (return, break, continue, or panic!):

let number: u32 = match text.parse::<u32>() {
    Ok(n)  => n,             <-- Arm 1 has type `u32`
    Err(_) => return,        <-- Arm 2 (`return`) has type `!`, which coerces to `u32`!
};                           ==> Entire `match` expression has type `u32`!
fn process_positive(input: Option<i32>) -> i32 {
    // 1. Inside `match`: `Some(v)` is `i32`, while `return 0` is `!` (coerces to `i32`)
    let val: i32 = match input {
        Some(v) => v,
        None => return 0,
    };

    // 2. Inside `if` / `else`: `val * 2` is `i32`, while `panic!(...)` is `!` (coerces to `i32`)
    let doubled: i32 = if val > 0 {
        val * 2
    } else {
        panic!("expected a strictly positive integer, got {val}");
    };

    doubled
}

fn main() {
    println!("Some(21) -> {}", process_positive(Some(21)));
    println!("None     -> {}", process_positive(None));
}
Some(21) -> 42
None     -> 0

Without the ! type coercing into i32, you would not be able to write return, break, continue, or panic! inside one branch of a value-producing match or if expression!

Diverging Functions (fn name(...) -> !)

You can use -> ! explicitly as the return type of a function to declare a diverging function—a function that is statically guaranteed never to return to its caller:

fn fatal_error(code: i32, message: &str) -> ! {
    eprintln!("Fatal error ({code}): {message}");
    std::process::exit(code);
}

fn server_loop(max_requests: u32) -> ! {
    let mut handled = 0;
    loop {
        handled += 1;
        if handled >= max_requests {
            fatal_error(0, "Reached max requests; shutting down cleanly.");
        }
    }
}

fn main() {
    let port: u16 = if true {
        8080
    } else {
        // Allowed inside an `u16` branch because `fatal_error` returns `!`:
        fatal_error(1, "invalid configuration")
    };

    println!("Server configured on port {port}");
}
Server configured on port 8080

Compile-Time Verification of -> ! Functions (E0308)

If a function declares -> ! as its return type, the compiler verifies that no control-flow path can reach the end of the function body or return a value:

// ❌ does not compile
fn bad_diverge(flag: bool) -> ! {
    if flag {
        panic!("diverged!");
    }
    // Error: when `flag == false`, control reaches the end of the function and implicitly returns `()`!
}

fn main() {}
error[E0308]: mismatched types
 --> src/main.rs:1:31
  |
1 | fn bad_diverge(flag: bool) -> ! {
  |    -----------                ^ expected `!`, found `()`
  |    |
  |    implicitly returns `()` as its body has no tail or `return` expression

error: aborting due to 1 previous error

Uninhabited Enums (std::convert::Infallible)

Before ! can be used directly in every generic trait argument position on stable Rust, the standard library also defines an empty zero-variant enum std::convert::Infallible (pub enum Infallible {}).

Just like !, an enum with zero variants cannot be constructed at runtime. Standard library conversions that can never fail—such as converting u8 to u32 via TryFrom—use Result<T, Infallible>. Because the Err(Infallible) variant can never exist, the compiler knows Result<T, Infallible> is guaranteed to be Ok(T) and optimizes away the error branch completely!