Option<T> and Result<T, E> in Depth

Algebraic Data Types

An Algebraic Data Type (ADT) is a composite type formed by combining other types using two fundamental algebraic operations: products (structs and tuples, which hold fields $A \text{ AND } B$ simultaneously) and sums (enums, which hold variant $A \text{ OR } B$ exclusively).

Sum Types vs. Product Types and Cardinality

The "algebra" in Algebraic Data Types refers to the number of distinct values (the cardinality, $|T|$) a type can inhabit:

Algebraic Form Rust Construct Cardinality Formula Example (bool has $|bool| = 2$, u8 has $|u8| = 256$)
Zero (Additive Identity) enum Never {} / ! $0$ Uninhabited type (cannot be instantiated)
One (Multiplicative Identity) () or struct Unit; $1$ Exactly one value: ()
Product Type ($A \times B$) (A, B) or struct S { a: A, b: B } $|A| \times |B|$ (bool, u8) has $2 \times 256 = 512$ possible values
Sum Type ($A + B$) enum E { Left(A), Right(B) } $|A| + |B|$ Result<u8, bool> has $256 + 2 = 258$ possible values

Option<T> and Result<T, E> as Canonical Sum Types

In the standard library, Option<T> and Result<T, E> are generic sum types that encode optionality and fallibility directly in the type system instead of using ambient null pointers or untracked exceptions:

pub enum Option<T> {
    None,       // 1 value  (1)
    Some(T),    // |T| values
}               // Total cardinality: |Option<T>| = 1 + |T|

pub enum Result<T, E> {
    Ok(T),      // |T| values
    Err(E),     // |E| values
}               // Total cardinality: |Result<T, E>| = |T| + |E|
Product Type: (T, E)                   Sum Type: Result<T, E>
+-------------------+                  +------------------------+
| Must contain BOTH |                  | Tag = 0 ──> Ok(T)      |
|   t: T   AND      |                  |   OR                   |
|   e: E            |                  | Tag = 1 ──> Err(E)     |
+-------------------+                  +------------------------+

Making Illegal States Unrepresentable with Sum Types

Replacing loosely coupled Option product fields (struct Connection { socket: Option<Socket>, error: Option<String> }, which permits invalid combinations like both None or both Some) with a sum type forces every state to be valid by construction, and the compiler enforces exhaustive pattern matching:

#[derive(Debug)]
enum ConnectionState {
    Connecting { attempt: u8 },
    Connected { session_id: u64 },
    Failed { reason: String },
}

fn describe(state: &ConnectionState) -> String {
    match state {
        ConnectionState::Connecting { attempt } => format!("Attempt #{attempt}"),
        ConnectionState::Connected { session_id } => format!("Active session {session_id}"),
        ConnectionState::Failed { reason } => format!("Error: {reason}"),
    }
}

fn main() {
    let states = [
        ConnectionState::Connecting { attempt: 1 },
        ConnectionState::Connected { session_id: 9001 },
        ConnectionState::Failed {
            reason: String::from("TLS handshake timeout"),
        },
    ];

    for s in &states {
        println!("{}", describe(s));
    }
}
Attempt #1
Active session 9001
Error: TLS handshake timeout