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

575: let Chains (&&)

Difficulty: 3 Level: Intermediate Chain multiple pattern checks with `&&` โ€” combine pattern matching and boolean conditions without nesting.

The Problem This Solves

Nested `if let` is an anti-pattern that you fall into when you need to match a pattern and check a condition and match another pattern. Each check adds a level of indentation. Three conditions in: you're three levels deep, the actual logic is at column 20, and the whole thing is hard to read. The alternative โ€” pulling values out with `match` or `unwrap`, then checking conditions โ€” scatters the validation across multiple statements. You've extracted `host` and `port` already; now you check if they're valid with separate `if` guards. The single logical "is this config valid?" check is fragmented. `let` chains (stabilized in Rust 1.88) solve this: all the pattern matches and boolean conditions sit in a single `if` condition, flat, left to right. The bindings from earlier `let` in the chain are available in later conditions and in the body.

The Intuition

A `let` chain is an `if` condition that interleaves `let pattern = expr` with `bool_expr`, all joined by `&&`. Like short-circuit evaluation: if any part fails, the whole condition is false and the else branch runs. But the bound variables from successful `let` bindings carry forward.
if let Ok(n) = parse(s) && n > 0 && n % 2 == 0 {
 // n is in scope here
}
This reads naturally: "if parsing succeeds, giving us `n`, AND `n` is positive, AND `n` is even." Three checks, one line, one binding. OCaml achieves this with `let` (monadic binding): `let n = parse s in let _ = guard (n > 0) in ...`. That's clean too, but requires a custom `let` operator. Rust's `let` chains are built-in syntax available in any `if` expression. The restriction: `let` chains can only appear in `if` and `while` conditions, not in standalone `let` statements (that's what `let-else` is for).

How It Works in Rust

// Three checks in one condition โ€” bindings available throughout
fn process(s: &str) -> Option<i32> {
 if let Ok(n) = s.parse::<i32>()  // pattern match
     && n > 0                      // boolean condition using n
     && n % 2 == 0                 // another boolean condition
 {
     Some(n * 2)
 } else {
     None
 }
}

// Multiple pattern lets in one chain
struct Config { host: Option<String>, port: Option<u16> }

fn make_addr(cfg: &Config) -> Option<String> {
 if let Some(ref host) = cfg.host   // bind host
     && let Some(port) = cfg.port   // bind port (note: second let)
     && !host.is_empty()            // use host from earlier
     && port > 0                    // use port from earlier
 {
     Some(format!("{}:{}", host, port))
 } else {
     None
 }
}

// while let chains โ€” loop until mismatch
fn first_positive_even(data: &[&str]) -> Option<i32> {
 for &s in data {
     if let Ok(n) = s.parse::<i32>()
         && n > 0
         && n % 2 == 0
     {
         return Some(n);
     }
 }
 None
}

What This Unlocks

Key Differences

ConceptOCamlRust
Chained conditions`let x = opt in let _ = guard cond in ...``if let Some(x) = opt && cond { }`
Multiple let bindingsRequires `let*` monad`let Some(a) = e1 && let Some(b) = e2`
Boolean mix-inGuards after bindingInline `&&` between pattern and bool
Scope of bindingsInside `in` chainIn the body of the `if`
AvailabilityVia ppx or custom operatorsStable since Rust 1.88
//! # Let Chains (&&)
//!
//! Chain multiple pattern checks with `&&` โ€” combine pattern matching
//! and boolean conditions without nesting.
//!
//! Requires Rust 1.88+ for stable let chains.

/// Process a string, returning doubled value if it's a positive even number.
///
/// Uses let chains to combine parsing, positivity check, and evenness check
/// in a single flat condition.
pub fn process(s: &str) -> Option<i32> {
    if let Ok(n) = s.parse::<i32>() && n > 0 && n % 2 == 0 {
        Some(n * 2)
    } else {
        None
    }
}

/// Alternative approach using traditional nested if-let (pre-1.88 style).
pub fn process_nested(s: &str) -> Option<i32> {
    if let Ok(n) = s.parse::<i32>() {
        if n > 0 {
            if n % 2 == 0 {
                return Some(n * 2);
            }
        }
    }
    None
}

/// Alternative approach using Option combinators.
pub fn process_combinators(s: &str) -> Option<i32> {
    s.parse::<i32>()
        .ok()
        .filter(|&n| n > 0)
        .filter(|&n| n % 2 == 0)
        .map(|n| n * 2)
}

/// Configuration with optional host and port.
#[derive(Debug, Clone)]
pub struct Config {
    pub host: Option<String>,
    pub port: Option<u16>,
}

impl Config {
    pub fn new(host: Option<String>, port: Option<u16>) -> Self {
        Self { host, port }
    }
}

/// Create an address string from config using let chains.
///
/// Validates that both host and port exist and are valid.
pub fn make_addr(cfg: &Config) -> Option<String> {
    if let Some(ref host) = cfg.host
        && let Some(port) = cfg.port
        && !host.is_empty()
        && port > 0
    {
        Some(format!("{}:{}", host, port))
    } else {
        None
    }
}

/// Alternative using Option::zip and filter.
pub fn make_addr_combinators(cfg: &Config) -> Option<String> {
    cfg.host
        .as_ref()
        .zip(cfg.port)
        .filter(|(host, port)| !host.is_empty() && *port > 0)
        .map(|(host, port)| format!("{}:{}", host, port))
}

/// Find the first positive even number in a slice of string representations.
pub fn first_positive_even(data: &[&str]) -> Option<i32> {
    for &s in data {
        if let Ok(n) = s.parse::<i32>() && n > 0 && n % 2 == 0 {
            return Some(n);
        }
    }
    None
}

/// Alternative using iterators.
pub fn first_positive_even_iter(data: &[&str]) -> Option<i32> {
    data.iter()
        .filter_map(|s| s.parse::<i32>().ok())
        .find(|&n| n > 0 && n % 2 == 0)
}

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

    #[test]
    fn test_process_valid_positive_even() {
        assert_eq!(process("4"), Some(8));
        assert_eq!(process("8"), Some(16));
        assert_eq!(process("100"), Some(200));
    }

    #[test]
    fn test_process_negative() {
        assert_eq!(process("-2"), None);
        assert_eq!(process("-4"), None);
    }

    #[test]
    fn test_process_odd() {
        assert_eq!(process("3"), None);
        assert_eq!(process("7"), None);
    }

    #[test]
    fn test_process_invalid_string() {
        assert_eq!(process("abc"), None);
        assert_eq!(process(""), None);
        assert_eq!(process("4.5"), None);
    }

    #[test]
    fn test_process_approaches_equivalent() {
        let test_cases = ["4", "-2", "3", "abc", "8", "0", "10"];
        for s in test_cases {
            assert_eq!(
                process(s),
                process_nested(s),
                "Mismatch for input: {}",
                s
            );
            assert_eq!(
                process(s),
                process_combinators(s),
                "Mismatch for input: {}",
                s
            );
        }
    }

    #[test]
    fn test_make_addr_valid() {
        let cfg = Config::new(Some("localhost".into()), Some(8080));
        assert_eq!(make_addr(&cfg), Some("localhost:8080".to_string()));
    }

    #[test]
    fn test_make_addr_empty_host() {
        let cfg = Config::new(Some("".into()), Some(8080));
        assert_eq!(make_addr(&cfg), None);
    }

    #[test]
    fn test_make_addr_missing_fields() {
        let cfg1 = Config::new(None, Some(80));
        let cfg2 = Config::new(Some("host".into()), None);
        assert_eq!(make_addr(&cfg1), None);
        assert_eq!(make_addr(&cfg2), None);
    }

    #[test]
    fn test_make_addr_zero_port() {
        let cfg = Config::new(Some("localhost".into()), Some(0));
        assert_eq!(make_addr(&cfg), None);
    }

    #[test]
    fn test_make_addr_approaches_equivalent() {
        let configs = [
            Config::new(Some("localhost".into()), Some(8080)),
            Config::new(Some("".into()), Some(8080)),
            Config::new(None, Some(80)),
            Config::new(Some("host".into()), None),
        ];
        for cfg in &configs {
            assert_eq!(make_addr(cfg), make_addr_combinators(cfg));
        }
    }

    #[test]
    fn test_first_positive_even() {
        assert_eq!(first_positive_even(&["x", "3", "-4", "6", "8"]), Some(6));
        assert_eq!(first_positive_even(&["1", "3", "5"]), None);
        assert_eq!(first_positive_even(&["-2", "-4"]), None);
        assert_eq!(first_positive_even(&["2"]), Some(2));
    }

    #[test]
    fn test_first_positive_even_approaches_equivalent() {
        let test_cases: &[&[&str]] = &[
            &["x", "3", "-4", "6", "8"],
            &["1", "3", "5"],
            &["-2", "-4"],
            &["2"],
            &[],
        ];
        for data in test_cases {
            assert_eq!(first_positive_even(data), first_positive_even_iter(data));
        }
    }
}
(* OCaml: flat chaining with let* *)
let (let*) = Option.bind

let process s =
  let* n = (try Some(int_of_string s) with _->None) in
  let* p = (if n > 0 then Some n else None) in
  let* e = (if p mod 2 = 0 then Some p else None) in
  Some (e * 2)

let () =
  List.iter (fun s ->
    match process s with
    | Some v -> Printf.printf "%s -> %d\n" s v
    | None   -> Printf.printf "%s -> invalid\n" s
  ) ["4";"-2";"3";"abc";"8"]