๐Ÿฆ€ Functional Rust
๐ŸŽฌ Pattern Matching Exhaustive match, destructuring, guards, let-else โ€” compiler-verified branching.
๐Ÿ“ Text version (for readers / accessibility)

โ€ข match is exhaustive โ€” the compiler ensures every variant is handled

โ€ข Destructuring extracts fields from structs, tuples, and enums in one step

โ€ข Guards (if conditions) add extra logic to match arms

โ€ข if let and let-else provide concise single-pattern matching

โ€ข Nested patterns and @ bindings handle complex data shapes elegantly

573: let-else for Early Return

Difficulty: 2 Level: Beginner Unwrap a pattern or bail out โ€” keep the happy path flat without nesting.

The Problem This Solves

Parsing and validation code suffers from rightward drift. You check if a config line has the right format โ€” that's an `if let`. Inside that, you parse the port โ€” another `if let`. Inside that, you validate the range โ€” another `if let`. By the third check you're two or three levels deep, and the actual logic is buried at the center of a pyramid. The classic fix is `match` with an explicit `return` or `continue` in the `None`/`Err` arm. But that's verbose: four lines for what should be one. Another option is `.ok_or()` and `?` chaining, which works when you're in a `Result`-returning context โ€” but not in loops, not in `void` functions, not when the fallback is `continue` or `break`. `let-else` is the dedicated solution for the "unwrap or bail" pattern. One line, flat code, no nesting.

The Intuition

`let Some(x) = opt else { return; };` reads as: "let `x` be the inner value of `opt`, otherwise (in the else branch) do this and diverge." The `else` block must diverge โ€” it has to `return`, `break`, `continue`, or `panic!`. After the `let-else`, `x` is in scope and you're guaranteed it matched. Compare to `if let`:
if let Some(x) = opt {
 // x is only in scope here
 use(x);
}
With `let-else`, `x` is in scope after the statement, not inside a block. That's the key difference. The happy path stays at the top level; failure is handled locally and immediately. OCaml handles this via `let*` (Option.bind chaining) or explicit match. The `let-else` idiom is Rust-specific and was stabilized in Rust 1.65 (2022).

How It Works in Rust

// Parse a "host:port" string โ€” bail on any malformation
fn parse_config(line: &str) -> Option<(String, u16)> {
 let parts: Vec<&str> = line.split(':').collect();

 // Destructure into exactly two parts, or return None
 let [host, port_str] = parts.as_slice() else { return None; };

 // Parse the port, or return None
 let Ok(port) = port_str.parse::<u16>() else { return None; };

 Some((host.to_string(), port))  // flat โ€” no nesting
}

// let-else in a loop โ€” use continue instead of return
fn sum_valid(inputs: &[&str]) -> i32 {
 let mut total = 0;
 for &s in inputs {
     let Ok(n) = s.parse::<i32>() else {
         eprintln!("skip: {}", s);
         continue;  // diverge with continue, not return
     };
     total += n;  // n is in scope here, at the loop's top level
 }
 total
}

// let-else with enum โ€” skip non-admin users
fn admin_name(users: &[User], id: u64) -> Option<&str> {
 let Some(u) = users.iter().find(|u| u.id == id) else { return None; };
 if !u.admin { return None; }
 Some(&u.name)
}

What This Unlocks

Key Differences

ConceptOCamlRust
Bail-on-none`let x = opt in ...` (bind chains)`let Some(x) = opt else { return; };`
Flat happy pathVia monadic `let` chainingVia sequential `let-else` statements
In loopsRecursive/List.filter`let ... else { continue; }`
Scope of bindingInside `in` blockAfter the `let-else` statement
Divergence requiredN/AElse branch must diverge (return/break/continue/panic)
fn parse_port(s: &str) -> Option<u16> {
    s.parse().ok().filter(|&p: &u16| p > 0)
}

fn parse_config(line: &str) -> Option<(String, u16)> {
    let parts: Vec<&str> = line.split(':').collect();
    let [host, port_str] = parts.as_slice() else { return None; };
    let Ok(port) = port_str.parse::<u16>() else { return None; };
    Some((host.to_string(), port))
}

// let-else in a loop
fn sum_valid(inputs: &[&str]) -> i32 {
    let mut total = 0;
    for &s in inputs {
        let Ok(n) = s.parse::<i32>() else {
            eprintln!("skip: {}", s); continue;
        };
        total += n;
    }
    total
}

// let-else with complex enum
#[derive(Debug)]
struct User { id: u64, name: String, admin: bool }

fn admin_name(users: &[User], id: u64) -> Option<&str> {
    let Some(u) = users.iter().find(|u| u.id == id) else { return None; };
    if !u.admin { return None; }
    Some(&u.name)
}

fn main() {
    for line in ["localhost:8080","bad","host:notaport","example.com:443"] {
        match parse_config(line) {
            Some((h,p)) => println!("-> {}:{}", h, p),
            None        => println!("invalid: {}", line),
        }
    }
    println!("sum valid: {}", sum_valid(&["1","two","3","four","5"]));
    let users = vec![
        User{id:1,name:"Alice".into(),admin:true},
        User{id:2,name:"Bob".into(),  admin:false},
    ];
    println!("admin 1: {:?}", admin_name(&users, 1));
    println!("admin 2: {:?}", admin_name(&users, 2));
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test] fn test_parse() { assert!(parse_config("h:80").is_some()); assert!(parse_config("bad").is_none()); }
    #[test] fn test_sum()   { assert_eq!(sum_valid(&["1","x","2"]), 3); }
}
(* OCaml option-chaining to avoid nesting *)
let (let*) = Option.bind

let parse_port s =
  let* n = (try Some(int_of_string s) with _ -> None) in
  if n > 0 && n < 65536 then Some n else None

let parse_config line =
  match String.split_on_char ':' line with
  | [host; port_str] ->
    let* port = parse_port port_str in
    Some (host, port)
  | _ -> None

let () =
  List.iter (fun line ->
    match parse_config line with
    | Some(h,p) -> Printf.printf "-> %s:%d\n" h p
    | None      -> Printf.printf "invalid: %s\n" line
  ) ["localhost:8080";"bad";"host:notaport";"example.com:443"]