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!