๐Ÿฆ€ Functional Rust
๐ŸŽฌ Error Handling in Rust Option, Result, the ? operator, and combinators.
๐Ÿ“ Text version (for readers / accessibility)

โ€ข Option represents a value that may or may not exist โ€” Some(value) or None

โ€ข Result represents success (Ok) or failure (Err) โ€” no exceptions needed

โ€ข The ? operator propagates errors up the call stack concisely

โ€ข Combinators like .map(), .and_then(), .unwrap_or() chain fallible operations

โ€ข The compiler forces you to handle every error case โ€” no silent failures

308: When to panic vs Return Result

Difficulty: 2 Level: Intermediate Know the one rule that tells you which to use โ€” every time.

The Problem This Solves

New Rust developers often get this wrong in both directions: using `panic!` for things callers should handle (input validation, missing files), or returning `Result` for things that literally cannot happen given correct code (post-assertion invariants, unreachable branches). Both mistakes make code harder to use and harder to reason about. The confusion comes from treating `panic!` as "throws an exception" โ€” but it isn't. A panic in Rust is a signal that the program has encountered a bug in the calling code, not a recoverable runtime condition. It's the equivalent of `assert false` in OCaml or `assert` in C โ€” a programmer error, not a user error. The rule is simple: panic for bugs, `Result` for expected failure modes. If a user can trigger it (bad input, missing file, network timeout), it's a `Result`. If only a programmer can trigger it (passing the wrong index, violating a documented precondition), it's a panic. Libraries should almost never panic โ€” they serve callers who can't recover from unexpected panics.

The Intuition

Panic = "you called this wrong" (a bug in the caller). `Result` = "this might fail for reasons outside your control" (expected failure).

How It Works in Rust

// Result: user-triggered failure โ€” caller decides how to handle
fn parse_age(s: &str) -> Result<u8, String> {
 let n: i32 = s.parse().map_err(|_| format!("'{}' is not a number", s))?;
 if n < 0 || n > 150 {
     return Err(format!("age {} out of range", n));
 }
 Ok(n as u8)
}

// Panic: programmer-triggered failure โ€” documents a contract violation
fn get_first(slice: &[i32]) -> i32 {
 assert!(!slice.is_empty(), "get_first: slice must not be empty");
 slice[0]
}

// unreachable!: a branch the programmer has proven can't be reached
fn classify(n: u8) -> &'static str {
 match n {
     0 => "zero",
     1..=9 => "single digit",
     10..=99 => "double digit",
     _ => "triple digit",
 }
}

// When it's OK to use unwrap():
// - In tests (test failures are expected to be loud)
// - In examples and prototypes
// - When you've already checked the condition and the type system can't see it
// - With expect() to document why it should be safe
let val: i32 = "42".parse().expect("hardcoded literal is always valid");
The `expect("reason")` pattern over bare `unwrap()` is important in production: it documents why the programmer believes this can't fail, making future debugging easier if they're wrong.

What This Unlocks

Key Differences

ConceptOCamlRust
Recoverable errorException or `Result` type`Result<T, E>`
Programmer bug`assert false` / `failwith``panic!`, `assert!`, `unreachable!`
Library codeExceptions acceptable`Result` strongly preferred โ€” panics are rarely acceptable
Pre-condition violationManual assertion`assert!` or `panic!` with message
TestsOUnit assertion`#[should_panic]` for expected panics
//! 308. When to panic vs return Result
//!
//! Guidelines: Result for recoverable errors, panic for programming bugs/invariant violations.

/// Library function: user provides invalid input -> use Result
fn parse_age(s: &str) -> Result<u8, String> {
    let n: i32 = s.parse().map_err(|_| format!("'{}' is not a number", s))?;
    if n < 0 || n > 150 {
        return Err(format!("age {} is out of range [0, 150]", n));
    }
    Ok(n as u8)
}

/// Internal function: programmer error -> can panic
fn get_element<T>(arr: &[T], index: usize) -> &T {
    // Panicking is OK here: wrong index is a programming bug, not user error
    &arr[index] // will panic with descriptive message if out of bounds
}

/// Invariant that must always hold internally
fn divide(a: i32, b: i32) -> i32 {
    assert!(b != 0, "divide: b must not be zero (caller's responsibility)");
    a / b
}

/// Application entry point: can convert errors to panics
fn process_user_input(age_str: &str) {
    match parse_age(age_str) {
        Ok(age) => println!("Valid age: {}", age),
        Err(e)  => println!("Invalid input: {}", e),
    }
}

fn main() {
    // Result for user-facing errors
    println!("--- Result pattern ---");
    process_user_input("25");
    process_user_input("200");
    process_user_input("abc");

    // Panic for programming bugs
    println!("--- Panic pattern ---");
    let arr = [1i32, 2, 3, 4, 5];
    println!("Element 2: {}", get_element(&arr, 2));

    // unwrap() in tests/prototypes is OK; in production prefer ?
    let result: Result<i32, &str> = Ok(42);
    let val = result.expect("this should never be Err in production code");
    println!("Expected value: {}", val);

    // unreachable! for exhaustive match that should never hit a branch
    let x = 5u32;
    let _msg = match x {
        0 => "zero",
        1..=9 => "single digit",
        _ => "multi-digit",
    };
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_parse_age_valid() {
        assert_eq!(parse_age("25"), Ok(25));
        assert_eq!(parse_age("0"), Ok(0));
        assert_eq!(parse_age("150"), Ok(150));
    }

    #[test]
    fn test_parse_age_invalid() {
        assert!(parse_age("abc").is_err());
        assert!(parse_age("200").is_err());
        assert!(parse_age("-1").is_err());
    }

    #[test]
    fn test_get_element() {
        let arr = [10i32, 20, 30];
        assert_eq!(*get_element(&arr, 1), 20);
    }

    #[test]
    #[should_panic]
    fn test_get_element_panics_out_of_bounds() {
        let arr = [1i32];
        get_element(&arr, 99); // should panic
    }
}
(* 308. When to panic vs return Result - OCaml *)

(* Result for recoverable failures *)
let safe_divide a b =
  if b = 0 then Error "division by zero" else Ok (a / b)

(* Exception/assert for bugs *)
let get_element arr i =
  if i < 0 || i >= Array.length arr
  then failwith (Printf.sprintf "index %d out of bounds" i)
  else arr.(i)

let () =
  (* Recoverable error *)
  (match safe_divide 10 0 with
  | Ok n -> Printf.printf "Result: %d\n" n
  | Error e -> Printf.printf "Error: %s\n" e);

  (* Programming bug - assert *)
  let arr = [|1; 2; 3|] in
  Printf.printf "Element: %d\n" (get_element arr 1);

  (* Validated input with Result *)
  let parse_age s =
    match int_of_string_opt s with
    | None -> Error "not a number"
    | Some n when n < 0 || n > 150 -> Error "age out of range"
    | Some n -> Ok n
  in
  (match parse_age "25" with Ok a -> Printf.printf "Age: %d\n" a | Error e -> print_endline e);
  (match parse_age "200" with Ok a -> Printf.printf "Age: %d\n" a | Error e -> print_endline e)