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

003: Pattern Matching

Difficulty: โญโญ Level: Intermediate Replace if/else chains with exhaustive matching โ€” and let the compiler tell you when you missed a case.

The Problem This Solves

Imagine you're handling different types of shapes, network packets, or UI events. The traditional approach: a chain of `if type == "circle" ... else if type == "rectangle" ...`. This is fragile: add a new type, forget to update the handler, and nothing tells you until a bug surfaces in production. Rust's `match` expression changes the contract: the compiler checks that you've handled every case. Add a new variant to your `enum`, and every `match` on that type becomes a compile error until you handle it. Miss a case โ€” the program doesn't build. This makes refactoring safe. It makes adding features explicit. It's one of the most practically useful things in Rust.

The Intuition

# Python (3.10+) โ€” match/case exists, but no exhaustiveness check
def area(shape):
 match shape['type']:
     case 'circle':   return math.pi * shape['r'] ** 2
     case 'rectangle': return shape['w'] * shape['h']
     # Forgot triangles? Python doesn't notice. Returns None silently.
// Java โ€” instanceof checks, easy to miss a case
if (shape instanceof Circle c) { ... }
else if (shape instanceof Rectangle r) { ... }
// Add Triangle later? Nothing reminds you to handle it here.
// Rust โ€” exhaustive match, compile error if you miss a variant
match shape {
 Shape::Circle(r)         => std::f64::consts::PI * r * r,
 Shape::Rectangle(w, h)   => w * h,
 Shape::Triangle(a, b, c) => { /* Heron's formula */ }
 // Miss any variant โ†’ compiler error: "non-exhaustive patterns"
}

How It Works in Rust

Define your type as an `enum`:
#[derive(Debug)]
enum Shape {
 Circle(f64),             // holds: radius
 Rectangle(f64, f64),     // holds: width, height
 Triangle(f64, f64, f64), // holds: three sides
}
Match on it โ€” binding the inner values directly in the pattern:
fn area(shape: &Shape) -> f64 {
 match shape {
     Shape::Circle(r) => std::f64::consts::PI * r * r,
     Shape::Rectangle(w, h) => w * h,
     Shape::Triangle(a, b, c) => {
         let s = (a + b + c) / 2.0;
         (s * (s - a) * (s - b) * (s - c)).sqrt()
     }
 }
}
Guard clauses โ€” add extra conditions inside patterns:
fn describe(shape: &Shape) -> String {
 match shape {
     Shape::Rectangle(w, h) if (w - h).abs() < f64::EPSILON => {
         format!("Square with side {w}")  // special case: equal sides
     }
     Shape::Rectangle(w, h) => format!("Rectangle {w}ร—{h}"),
     Shape::Circle(r) => format!("Circle with radius {r}"),
     Shape::Triangle(a, b, c) => format!("Triangle with sides {a}, {b}, {c}"),
 }
}
Match anywhere โ€” not just in standalone statements, but inside `.map()`, closures, `if let`:
// Scale all shapes using match inside a closure
let scaled: Vec<Shape> = shapes.iter().map(|s| match s {
 Shape::Circle(r)         => Shape::Circle(r * 2.0),
 Shape::Rectangle(w, h)   => Shape::Rectangle(w * 2.0, h * 2.0),
 Shape::Triangle(a, b, c) => Shape::Triangle(a * 2.0, b * 2.0, c * 2.0),
}).collect();

What This Unlocks

Key Differences

ConceptOCamlRust
Custom type`type shape = Circle of float \...``enum Shape { Circle(f64), ... }`
Match expression`match shape with \Circle r -> ...``match shape { Shape::Circle(r) => ... }`
ExhaustivenessEnforced by compilerEnforced by compiler
Guard clauses`when condition``if condition` (after pattern)
Binding names`\Circle r ->` (direct)`Shape::Circle(r) =>` (qualified)
Nested matchNaturalNatural โ€” `match` works in any expression
/// Pattern Matching: the heart of both OCaml and Rust.
///
/// Both languages use pattern matching as a primary control flow mechanism.
/// OCaml has algebraic data types; Rust has enums. The mapping is remarkably direct.

// โ”€โ”€ Define a Shape type (algebraic data type / enum) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

#[derive(Debug, Clone, PartialEq)]
pub enum Shape {
    Circle(f64),              // radius
    Rectangle(f64, f64),      // width, height
    Triangle(f64, f64, f64),  // three sides
}

// โ”€โ”€ Idiomatic Rust: match expressions โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

/// Calculate area using match โ€” direct analog of OCaml's pattern matching
pub fn area(shape: &Shape) -> f64 {
    match shape {
        Shape::Circle(r) => std::f64::consts::PI * r * r,
        Shape::Rectangle(w, h) => w * h,
        Shape::Triangle(a, b, c) => {
            // Heron's formula
            let s = (a + b + c) / 2.0;
            (s * (s - a) * (s - b) * (s - c)).sqrt()
        }
    }
}

/// Describe a shape โ€” demonstrates string formatting in match arms
pub fn describe(shape: &Shape) -> String {
    match shape {
        Shape::Circle(r) => format!("Circle with radius {r}"),
        Shape::Rectangle(w, h) if (w - h).abs() < f64::EPSILON => {
            format!("Square with side {w}")
        }
        Shape::Rectangle(w, h) => format!("Rectangle {w}ร—{h}"),
        Shape::Triangle(a, b, c) if (a - b).abs() < f64::EPSILON
            && (b - c).abs() < f64::EPSILON => {
            format!("Equilateral triangle with side {a}")
        }
        Shape::Triangle(a, b, c) => format!("Triangle with sides {a}, {b}, {c}"),
    }
}

// โ”€โ”€ Nested pattern matching with Option โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

/// Find the largest shape by area from an optional list
pub fn largest_area(shapes: &[Shape]) -> Option<f64> {
    // Uses iterator + fold, but the interesting bit is Option handling
    shapes.iter()
        .map(|s| area(s))
        .fold(None, |max, a| match max {
            None => Some(a),
            Some(m) if a > m => Some(a),
            _ => max,
        })
}

// โ”€โ”€ Recursive style with exhaustive matching โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

/// Count shapes of each type โ€” recursive traversal with pattern matching
pub fn count_by_type(shapes: &[Shape]) -> (usize, usize, usize) {
    fn aux(shapes: &[Shape], c: usize, r: usize, t: usize) -> (usize, usize, usize) {
        match shapes.split_first() {
            None => (c, r, t),
            Some((Shape::Circle(_), rest)) => aux(rest, c + 1, r, t),
            Some((Shape::Rectangle(_, _), rest)) => aux(rest, c, r + 1, t),
            Some((Shape::Triangle(_, _, _), rest)) => aux(rest, c, r, t + 1),
        }
    }
    aux(shapes, 0, 0, 0)
}

// โ”€โ”€ Functional style with iterators โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

/// Scale all shapes by a factor โ€” map with pattern matching inside
pub fn scale_all(shapes: &[Shape], factor: f64) -> Vec<Shape> {
    shapes.iter().map(|s| match s {
        Shape::Circle(r) => Shape::Circle(r * factor),
        Shape::Rectangle(w, h) => Shape::Rectangle(w * factor, h * factor),
        Shape::Triangle(a, b, c) => Shape::Triangle(a * factor, b * factor, c * factor),
    }).collect()
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::f64::consts::PI;

    #[test]
    fn test_area_circle() {
        let c = Shape::Circle(5.0);
        assert!((area(&c) - PI * 25.0).abs() < 1e-10);
    }

    #[test]
    fn test_area_rectangle() {
        assert!((area(&Shape::Rectangle(3.0, 4.0)) - 12.0).abs() < 1e-10);
    }

    #[test]
    fn test_area_triangle() {
        // 3-4-5 right triangle, area = 6
        assert!((area(&Shape::Triangle(3.0, 4.0, 5.0)) - 6.0).abs() < 1e-10);
    }

    #[test]
    fn test_describe_with_guards() {
        assert_eq!(describe(&Shape::Rectangle(5.0, 5.0)), "Square with side 5");
        assert_eq!(describe(&Shape::Triangle(3.0, 3.0, 3.0)), "Equilateral triangle with side 3");
        assert!(describe(&Shape::Rectangle(3.0, 4.0)).contains("ร—"));
    }

    #[test]
    fn test_largest_area_empty() {
        assert_eq!(largest_area(&[]), None);
    }

    #[test]
    fn test_largest_area_nonempty() {
        let shapes = vec![Shape::Circle(1.0), Shape::Rectangle(10.0, 10.0)];
        assert!((largest_area(&shapes).unwrap() - 100.0).abs() < 1e-10);
    }

    #[test]
    fn test_count_by_type() {
        let shapes = vec![
            Shape::Circle(1.0), Shape::Circle(2.0),
            Shape::Rectangle(1.0, 2.0),
            Shape::Triangle(3.0, 4.0, 5.0),
        ];
        assert_eq!(count_by_type(&shapes), (2, 1, 1));
        assert_eq!(count_by_type(&[]), (0, 0, 0));
    }

    #[test]
    fn test_scale() {
        let shapes = vec![Shape::Circle(2.0), Shape::Rectangle(3.0, 4.0)];
        let scaled = scale_all(&shapes, 2.0);
        assert_eq!(scaled[0], Shape::Circle(4.0));
        assert_eq!(scaled[1], Shape::Rectangle(6.0, 8.0));
    }
}

fn main() {
    println!("{:?}", (area(&c) - PI * 25.0).abs() < 1e-10);
    println!("{:?}", (area(&Shape::Rectangle(3.0, 4.0)) - 12.0).abs() < 1e-10);
    println!("{:?}", (area(&Shape::Triangle(3.0, 4.0, 5.0)) - 6.0).abs() < 1e-10);
}
(* Pattern Matching: OCaml's most powerful feature *)

(* โ”€โ”€ Algebraic data type for shapes โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ *)

type shape =
  | Circle of float
  | Rectangle of float * float
  | Triangle of float * float * float

(* โ”€โ”€ Area calculation via pattern matching โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ *)

let area = function
  | Circle r -> Float.pi *. r *. r
  | Rectangle (w, h) -> w *. h
  | Triangle (a, b, c) ->
    let s = (a +. b +. c) /. 2.0 in
    sqrt (s *. (s -. a) *. (s -. b) *. (s -. c))

(* โ”€โ”€ Description with guard patterns โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ *)

let describe = function
  | Circle r -> Printf.sprintf "Circle with radius %g" r
  | Rectangle (w, h) when Float.equal w h ->
    Printf.sprintf "Square with side %g" w
  | Rectangle (w, h) ->
    Printf.sprintf "Rectangle %gร—%g" w h
  | Triangle (a, b, c) when Float.equal a b && Float.equal b c ->
    Printf.sprintf "Equilateral triangle with side %g" a
  | Triangle (a, b, c) ->
    Printf.sprintf "Triangle with sides %g, %g, %g" a b c

(* โ”€โ”€ Largest area using fold + Option โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ *)

let largest_area shapes =
  List.fold_left
    (fun acc s ->
       let a = area s in
       match acc with
       | None -> Some a
       | Some m when a > m -> Some a
       | _ -> acc)
    None shapes

(* โ”€โ”€ Count by type โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ *)

let count_by_type shapes =
  List.fold_left
    (fun (c, r, t) s -> match s with
       | Circle _ -> (c + 1, r, t)
       | Rectangle _ -> (c, r + 1, t)
       | Triangle _ -> (c, r, t + 1))
    (0, 0, 0) shapes

(* โ”€โ”€ Scale all shapes โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ *)

let scale_all factor = List.map (function
  | Circle r -> Circle (r *. factor)
  | Rectangle (w, h) -> Rectangle (w *. factor, h *. factor)
  | Triangle (a, b, c) -> Triangle (a *. factor, b *. factor, c *. factor))

(* โ”€โ”€ Tests โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ *)
let () =
  assert (abs_float (area (Circle 5.0) -. Float.pi *. 25.0) < 1e-10);
  assert (abs_float (area (Rectangle (3.0, 4.0)) -. 12.0) < 1e-10);
  assert (abs_float (area (Triangle (3.0, 4.0, 5.0)) -. 6.0) < 1e-10);
  assert (describe (Rectangle (5.0, 5.0)) = "Square with side 5");
  assert (largest_area [] = None);
  assert (count_by_type [Circle 1.0; Circle 2.0; Rectangle (1.0, 2.0)] = (2, 1, 0));
  print_endline "โœ“ All pattern matching tests passed"

๐Ÿ“Š Detailed Comparison

Pattern Matching: OCaml vs Rust

The Core Insight

Pattern matching is where OCaml and Rust feel most alike. Both languages use algebraic data types (OCaml variants / Rust enums) with exhaustive matching โ€” the compiler ensures every case is handled. This eliminates entire classes of bugs that plague languages without sum types.

OCaml Approach

OCaml's variant types and `match`/`function` expressions are the language's crown jewel:
๐Ÿช Show OCaml equivalent
type shape = Circle of float | Rectangle of float * float

let area = function
| Circle r -> Float.pi *. r *. r
| Rectangle (w, h) -> w *. h
Guard clauses (`when`) add conditional logic within patterns. The compiler warns on non-exhaustive matches and unused cases โ€” a safety net that catches bugs at compile time.

Rust Approach

Rust's `enum` + `match` is a direct descendant of ML-family pattern matching:
enum Shape { Circle(f64), Rectangle(f64, f64) }

fn area(shape: &Shape) -> f64 {
 match shape {
     Shape::Circle(r) => PI * r * r,
     Shape::Rectangle(w, h) => w * h,
 }
}
Rust adds ownership to the mix: matching can move, borrow, or copy inner values. The `ref` keyword and `&` patterns control this explicitly.

Key Differences

AspectOCamlRust
Sum types`type t = A \B of int``enum T { A, B(i32) }`
ExhaustivenessCompiler warningCompiler error (stricter)
Guards`when` clause`if` guard
BindingAutomatic copyMove/borrow semantics
Nested matchNaturalNatural
Or-patterns`A \B -> ...``A \B => ...`
Wildcard`_``_`
`function` sugarYes (one-arg match)No equivalent

What Rust Learners Should Notice

  • Exhaustiveness is enforced: Unlike OCaml's warning, Rust makes non-exhaustive matches a hard error. This is stricter and safer.
  • Ownership in patterns: When you match on `Shape::Circle(r)`, `r` is a copy (for `f64`) or a move (for `String`). Use `&Shape::Circle(r)` or `ref` to borrow.
  • No `function` keyword: OCaml's `let f = function | A -> ... | B -> ...` has no Rust equivalent. You always write `fn f(x: T) -> U { match x { ... } }`.
  • Guards work the same: `Some(x) if x > 0 => ...` in Rust mirrors OCaml's `Some x when x > 0 -> ...`.

Further Reading

  • [The Rust Book โ€” Patterns and Matching](https://doc.rust-lang.org/book/ch18-00-patterns-and-matching.html)
  • [OCaml Manual โ€” Pattern Matching](https://v2.ocaml.org/manual/patterns.html)
  • [Rust Reference โ€” Enum types](https://doc.rust-lang.org/reference/items/enumerations.html)