๐Ÿฆ€ 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

293: The ? Operator

Difficulty: 2 Level: Intermediate The single most important operator for readable error handling in Rust โ€” it says "if this failed, return the error immediately."

The Problem This Solves

You're writing a function that calls three other functions, each of which might fail. Without `?`, you'd write this:
fn compute(a_str: &str, b_str: &str) -> Result<u32, AppError> {
 match parse_positive(a_str) {
     Ok(a) => match parse_positive(b_str) {
         Ok(b) => match safe_div(a, b) {
             Ok(result) => Ok(result * 2),
             Err(e) => Err(e),
         },
         Err(e) => Err(e),
     },
     Err(e) => Err(e),
 }
}
This is six lines of boilerplate that all say the same thing: "if it failed, pass the error up." Multiply that by every function in a real application and you have hundreds of lines of noise hiding your actual logic. The `?` operator collapses all that into a single character. Place `?` after any expression that returns `Result` or `Option`, and Rust does the match for you: if it's `Ok(x)`, unwrap to `x` and continue; if it's `Err(e)`, return `Err(e.into())` immediately. Your function reads like a happy-path-only description of what it does.

The Intuition

If you've written async JavaScript with `async/await`, you've seen this idea: instead of `.then().then().catch()`, you write straight-line code and errors bubble up automatically via `try/catch`. The `?` operator is Rust's equivalent โ€” but checked at compile time, with no exceptions thrown at runtime. Think of `?` as a supercharged early return: "try to get this value โ€” if it fails, bail out of the whole function with that error." It's not magic, it's just very convenient shorthand.

How It Works in Rust

use std::num::ParseIntError;

#[derive(Debug)]
enum AppError {
 Parse(ParseIntError),
 DivByZero,
 NegativeInput,
}

// This impl lets ? automatically convert ParseIntError into AppError
impl From<ParseIntError> for AppError {
 fn from(e: ParseIntError) -> Self { AppError::Parse(e) }
}

fn parse_positive(s: &str) -> Result<u32, AppError> {
 let n: i32 = s.parse()?;  // ? here: if parse fails, return Err(AppError::Parse(...))
                            // The From impl does the conversion automatically
 if n < 0 {
     Err(AppError::NegativeInput)
 } else {
     Ok(n as u32)
 }
}

fn safe_div(a: u32, b: u32) -> Result<u32, AppError> {
 if b == 0 { Err(AppError::DivByZero) } else { Ok(a / b) }
}

// Three ? operators, three potential early returns โ€” reads like straight-line code
fn compute(a_str: &str, b_str: &str) -> Result<u32, AppError> {
 let a = parse_positive(a_str)?;  // if this fails, return immediately
 let b = parse_positive(b_str)?;  // if this fails, return immediately
 let result = safe_div(a, b)?;    // if this fails, return immediately
 Ok(result * 2)                   // only get here if all three succeeded
}

// ? works on Option too
fn find_double(v: &[i32], target: i32) -> Option<i32> {
 let idx = v.iter().position(|&x| x == target)?;  // None if not found
 let val = v.get(idx)?;                             // None if out of bounds
 Some(val * 2)
}
What `?` desugars to under the hood:
// `expr?` expands to roughly:
match expr {
 Ok(val)  => val,
 Err(e)   => return Err(e.into()),  // .into() uses the From trait
}
The `.into()` is what makes `?` so flexible: as long as you implement `From<SourceError> for YourError`, `?` handles the conversion automatically.

What This Unlocks

Key Differences

ConceptOCamlRust
Error propagation`let* x = result in ...` (ppx_let)`let x = result?;`
Desugars to`Result.bind``match + return Err(e.into())`
Type conversionManualAutomatic via `From` trait
On OptionManual `match` or `Option.bind``option?` โ€” returns `None` early
Works in closuresYesLimited โ€” `?` only works in functions returning `Result`/`Option`
//! 293. ? operator and early returns
//!
//! `?` desugars to match + return Err(e.into()), enabling clean error propagation.

use std::num::ParseIntError;
use std::fmt;

#[derive(Debug)]
enum AppError {
    Parse(ParseIntError),
    DivByZero,
    NegativeInput,
}

impl fmt::Display for AppError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            AppError::Parse(e) => write!(f, "parse error: {}", e),
            AppError::DivByZero => write!(f, "division by zero"),
            AppError::NegativeInput => write!(f, "negative input"),
        }
    }
}

impl From<ParseIntError> for AppError {
    fn from(e: ParseIntError) -> Self {
        AppError::Parse(e)
    }
}

fn parse_positive(s: &str) -> Result<u32, AppError> {
    let n: i32 = s.parse()?; // ? auto-converts ParseIntError via From
    if n < 0 {
        Err(AppError::NegativeInput)
    } else {
        Ok(n as u32)
    }
}

fn safe_div(a: u32, b: u32) -> Result<u32, AppError> {
    if b == 0 { Err(AppError::DivByZero) } else { Ok(a / b) }
}

// Chain of ? operators
fn compute(a_str: &str, b_str: &str) -> Result<u32, AppError> {
    let a = parse_positive(a_str)?;
    let b = parse_positive(b_str)?;
    let result = safe_div(a, b)?;
    Ok(result * 2)
}

// ? on Option
fn find_double(v: &[i32], target: i32) -> Option<i32> {
    let idx = v.iter().position(|&x| x == target)?;
    let val = v.get(idx)?;
    Some(val * 2)
}

fn main() {
    println!("{:?}", compute("20", "4"));   // Ok(10)
    println!("{:?}", compute("abc", "4"));  // Err(Parse(...))
    println!("{:?}", compute("20", "0"));   // Err(DivByZero)
    println!("{:?}", compute("-5", "2"));   // Err(NegativeInput)

    let v = [1, 2, 3, 4, 5];
    println!("{:?}", find_double(&v, 3));   // Some(6)
    println!("{:?}", find_double(&v, 99));  // None
}

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

    #[test]
    fn test_compute_success() {
        assert_eq!(compute("20", "4"), Ok(10));
    }

    #[test]
    fn test_compute_parse_error() {
        assert!(matches!(compute("abc", "4"), Err(AppError::Parse(_))));
    }

    #[test]
    fn test_compute_div_zero() {
        assert!(matches!(compute("20", "0"), Err(AppError::DivByZero)));
    }

    #[test]
    fn test_question_mark_option() {
        let v = [1i32, 2, 3];
        assert_eq!(find_double(&v, 2), Some(4));
        assert_eq!(find_double(&v, 9), None);
    }
}
(* 293. ? operator and early returns - OCaml *)
(* OCaml: use let* with Result monad or explicit bind *)

let ( let* ) = Result.bind

let parse_and_add s1 s2 =
  let* a = match int_of_string_opt s1 with
    | Some n -> Ok n | None -> Error ("bad: " ^ s1) in
  let* b = match int_of_string_opt s2 with
    | Some n -> Ok n | None -> Error ("bad: " ^ s2) in
  Ok (a + b)

let () =
  (match parse_and_add "10" "20" with
  | Ok n -> Printf.printf "Sum: %d\n" n
  | Error e -> Printf.printf "Error: %s\n" e);

  (match parse_and_add "10" "abc" with
  | Ok n -> Printf.printf "Sum: %d\n" n
  | Error e -> Printf.printf "Error: %s\n" e);

  (* Option version *)
  let ( let* ) = Option.bind in
  let lookup env key =
    let* value = List.assoc_opt key env in
    let* n = int_of_string_opt value in
    Some (n * 2)
  in
  let env = [("x", "5"); ("y", "bad")] in
  (match lookup env "x" with Some n -> Printf.printf "x*2=%d\n" n | None -> print_endline "None");
  (match lookup env "y" with Some n -> Printf.printf "y*2=%d\n" n | None -> print_endline "None")