1012-never-type — The Never Type (!)
Tutorial
The Problem
Some computations never return a value: they panic, loop forever, or exit the process. In type theory, this is the "bottom" type — a type with no inhabitants. Rust makes this explicit with the ! type (pronounced "never"). Functions returning ! can appear in any expression position because they are coerced to any type, which allows them to unify with arbitrary branches in match expressions.
The never type also enables safe encoding of impossible states. std::convert::Infallible is an uninhabited enum (effectively !) used as the error type in infallible Results.
🎯 Learning Outcomes
! type means and why it is called the bottom type-> !! in match arms with unreachable!() and panic!() for exhaustivenessResult<T, Infallible> by matching on an empty enum! coerces to any type in expression contextCode Example
#![allow(clippy::all)]
// 1012: The Never Type (!)
// Diverging functions, match exhaustiveness, infallible conversions
use std::fmt;
// Approach 1: Diverging functions return !
fn diverge_panic() -> ! {
panic!("this never returns");
}
fn diverge_loop() -> ! {
// panic! diverges — satisfies -> ! without an infinite loop
panic!("for testing we break with panic");
}
// Approach 2: ! in match arms for exhaustiveness
fn handle_infallible(r: Result<i64, std::convert::Infallible>) -> i64 {
match r {
Ok(n) => n,
// Err branch is unreachable — Infallible can't be constructed
Err(e) => match e {}, // empty match on uninhabited type
}
}
// Approach 3: Using ! in enums and type positions
#[derive(Debug)]
enum MyResult<T, E> {
Ok(T),
Err(E),
}
// When E = std::convert::Infallible, Err is impossible
fn always_succeeds() -> Result<i64, std::convert::Infallible> {
Ok(42)
}
// Diverging in match arms
fn classify(n: i64) -> String {
match n {
n if n > 0 => format!("positive: {}", n),
n if n < 0 => format!("negative: {}", n),
0 => "zero".into(),
_ => unreachable!(), // returns !, unifies with String
}
}
// Custom error that can display but also show unreachable patterns
#[derive(Debug)]
enum ParseOrNever {
BadFormat(String),
}
impl fmt::Display for ParseOrNever {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ParseOrNever::BadFormat(s) => write!(f, "bad format: {}", s),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_infallible_result() {
let result = always_succeeds();
assert_eq!(handle_infallible(result), 42);
}
#[test]
fn test_classify() {
assert_eq!(classify(5), "positive: 5");
assert_eq!(classify(-3), "negative: -3");
assert_eq!(classify(0), "zero");
}
#[test]
#[should_panic(expected = "this never returns")]
fn test_diverge_panic() {
diverge_panic();
}
#[test]
#[should_panic]
fn test_diverge_loop() {
diverge_loop();
}
#[test]
fn test_never_coercion() {
// ! coerces to any type, so diverging expressions
// can appear in any type context
let _val: i64 = if true { 42 } else { panic!("never") };
assert_eq!(_val, 42);
}
#[test]
fn test_infallible_into() {
// Infallible implements Into<T> for all T... but we can't construct one
// This is the key insight: you can't create Infallible, so From/Into are vacuously true
let r: Result<i64, std::convert::Infallible> = Ok(99);
// unwrap is always safe on infallible results
assert_eq!(r.unwrap(), 99);
}
}Key Differences
! as a first-class type; OCaml encodes it as an empty variant or uses the polymorphic 'a return.std::convert::Infallible is the canonical uninhabited type; OCaml lacks a standard equivalent.match e {} syntax.! to any type in expression position; OCaml's 'a return type is universally polymorphic which achieves the same effect.OCaml Approach
OCaml has no explicit ! type in surface syntax, but the concept exists as the 'a type variable in functions like failwith : string -> 'a. An uninhabited type can be encoded as an empty variant:
type never = | (* empty variant — no constructors *)
let handle_never (r : (int, never) result) =
match r with
| Ok n -> n
| Error e -> match e with (* exhaustive because never has no cases *)
The Error arm is compiled away since it can never be entered.
Full Source
#![allow(clippy::all)]
// 1012: The Never Type (!)
// Diverging functions, match exhaustiveness, infallible conversions
use std::fmt;
// Approach 1: Diverging functions return !
fn diverge_panic() -> ! {
panic!("this never returns");
}
fn diverge_loop() -> ! {
// panic! diverges — satisfies -> ! without an infinite loop
panic!("for testing we break with panic");
}
// Approach 2: ! in match arms for exhaustiveness
fn handle_infallible(r: Result<i64, std::convert::Infallible>) -> i64 {
match r {
Ok(n) => n,
// Err branch is unreachable — Infallible can't be constructed
Err(e) => match e {}, // empty match on uninhabited type
}
}
// Approach 3: Using ! in enums and type positions
#[derive(Debug)]
enum MyResult<T, E> {
Ok(T),
Err(E),
}
// When E = std::convert::Infallible, Err is impossible
fn always_succeeds() -> Result<i64, std::convert::Infallible> {
Ok(42)
}
// Diverging in match arms
fn classify(n: i64) -> String {
match n {
n if n > 0 => format!("positive: {}", n),
n if n < 0 => format!("negative: {}", n),
0 => "zero".into(),
_ => unreachable!(), // returns !, unifies with String
}
}
// Custom error that can display but also show unreachable patterns
#[derive(Debug)]
enum ParseOrNever {
BadFormat(String),
}
impl fmt::Display for ParseOrNever {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
ParseOrNever::BadFormat(s) => write!(f, "bad format: {}", s),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_infallible_result() {
let result = always_succeeds();
assert_eq!(handle_infallible(result), 42);
}
#[test]
fn test_classify() {
assert_eq!(classify(5), "positive: 5");
assert_eq!(classify(-3), "negative: -3");
assert_eq!(classify(0), "zero");
}
#[test]
#[should_panic(expected = "this never returns")]
fn test_diverge_panic() {
diverge_panic();
}
#[test]
#[should_panic]
fn test_diverge_loop() {
diverge_loop();
}
#[test]
fn test_never_coercion() {
// ! coerces to any type, so diverging expressions
// can appear in any type context
let _val: i64 = if true { 42 } else { panic!("never") };
assert_eq!(_val, 42);
}
#[test]
fn test_infallible_into() {
// Infallible implements Into<T> for all T... but we can't construct one
// This is the key insight: you can't create Infallible, so From/Into are vacuously true
let r: Result<i64, std::convert::Infallible> = Ok(99);
// unwrap is always safe on infallible results
assert_eq!(r.unwrap(), 99);
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_infallible_result() {
let result = always_succeeds();
assert_eq!(handle_infallible(result), 42);
}
#[test]
fn test_classify() {
assert_eq!(classify(5), "positive: 5");
assert_eq!(classify(-3), "negative: -3");
assert_eq!(classify(0), "zero");
}
#[test]
#[should_panic(expected = "this never returns")]
fn test_diverge_panic() {
diverge_panic();
}
#[test]
#[should_panic]
fn test_diverge_loop() {
diverge_loop();
}
#[test]
fn test_never_coercion() {
// ! coerces to any type, so diverging expressions
// can appear in any type context
let _val: i64 = if true { 42 } else { panic!("never") };
assert_eq!(_val, 42);
}
#[test]
fn test_infallible_into() {
// Infallible implements Into<T> for all T... but we can't construct one
// This is the key insight: you can't create Infallible, so From/Into are vacuously true
let r: Result<i64, std::convert::Infallible> = Ok(99);
// unwrap is always safe on infallible results
assert_eq!(r.unwrap(), 99);
}
}
Deep Comparison
The Never Type — Comparison
Core Insight
Both languages have bottom types for diverging expressions. Rust makes it explicit with ! and Infallible; OCaml uses the polymorphic type variable 'a and empty variant types.
OCaml Approach
exit, raise, failwith have type 'a — universally quantified acts as bottomtype never = | are uninhabitable (OCaml 4.07+)'a in return position means "can unify with anything" — similar to !Rust Approach
! is the never type — functions returning ! divergestd::convert::Infallible is the stable uninhabited enum (0 variants)match e {} on uninhabited types requires no armsunreachable!() macro expands to panic! but returns !Comparison Table
| Aspect | OCaml | Rust |
|---|---|---|
| Bottom type | 'a (polymorphic) | ! (explicit) |
| Uninhabited type | type never = \| | Infallible / ! |
| Diverging function | Returns 'a | Returns ! |
| Empty match | Implicit (no constructors) | match e {} |
| Infallible Result | Not idiomatic | Result<T, Infallible> |
| Unreachable | assert false | unreachable!() |
Exercises
safe_index(v: &[i32], i: usize) -> i32 that panics with a custom message if i is out of bounds. Annotate a helper that builds the panic message as -> !.Result<u32, Infallible> producer that parses a hardcoded string known to always be valid. Use handle_infallible to unwrap it without unwrap().ParseOrNever enum variant Computed(i64) and add a match arm in a function that covers all variants — demonstrating exhaustive matching.