ExamplesBy LevelBy TopicLearning Paths
1012 Advanced

1012-never-type — The Never Type (!)

Functional Programming

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

  • • Understand what the ! type means and why it is called the bottom type
  • • Write diverging functions annotated with -> !
  • • Use ! in match arms with unreachable!() and panic!() for exhaustiveness
  • • Handle Result<T, Infallible> by matching on an empty enum
  • • Appreciate how ! coerces to any type in expression context
  • Code 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

  • Named bottom type: Rust has ! as a first-class type; OCaml encodes it as an empty variant or uses the polymorphic 'a return.
  • Standard library support: Rust's std::convert::Infallible is the canonical uninhabited type; OCaml lacks a standard equivalent.
  • Match exhaustiveness: Both compilers understand that an empty type requires no match arms, but Rust expresses this with match e {} syntax.
  • Coercion: Rust automatically coerces ! 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);
        }
    }
    ✓ Tests Rust test suite
    #[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 bottom
  • • Empty variant types type 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 ! diverge
  • std::convert::Infallible is the stable uninhabited enum (0 variants)
  • match e {} on uninhabited types requires no arms
  • unreachable!() macro expands to panic! but returns !
  • Comparison Table

    AspectOCamlRust
    Bottom type'a (polymorphic)! (explicit)
    Uninhabited typetype never = \|Infallible / !
    Diverging functionReturns 'aReturns !
    Empty matchImplicit (no constructors)match e {}
    Infallible ResultNot idiomaticResult<T, Infallible>
    Unreachableassert falseunreachable!()

    Exercises

  • Write a function 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 -> !.
  • Implement a Result<u32, Infallible> producer that parses a hardcoded string known to always be valid. Use handle_infallible to unwrap it without unwrap().
  • Add a new ParseOrNever enum variant Computed(i64) and add a match arm in a function that covers all variants — demonstrating exhaustive matching.
  • Open Source Repos