🦀 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

583: Const in Patterns

Difficulty: 2 Level: Beginner Match against named constants and associated constants in `match` expressions, making arms self-documenting and refactor-safe.

The Problem This Solves

Magic numbers in `match` arms are fragile and hard to read. When you write `match port { 80 => "HTTP", 443 => "HTTPS", ... }`, the meaning lives only in a comment, and if you later change `HTTP_PORT` from 80 to something else, you must hunt down every `match` that references it. Named constants in `match` arms solve both problems: the name documents intent, and changing the constant's value automatically updates every pattern that references it. This is especially important for domain-specific codes, protocol ports, timeout values, and configuration constants that appear in multiple places.

The Intuition

Rust allows `const` names in `match` patterns wherever a literal would work — for integers, booleans, chars, and other `PartialEq + Copy` types. The compiler replaces the constant with its value at compile time. Associated constants (`Type::CONST`) work the same way, letting you group related constants on a type rather than scattering them globally. One important caveat: a bare identifier in a `match` arm is a binding pattern, not a constant. To use a constant, it must be either a `const` item, a path (`Module::CONST`), or an associated constant (`Struct::CONST`). Single-segment `const` names work directly.

How It Works in Rust

Top-level constants in patterns:
const HTTP:  u16 = 80;
const HTTPS: u16 = 443;

fn describe_port(p: u16) -> &'static str {
 match p {
     HTTP  => "HTTP",
     HTTPS => "HTTPS",
     1..=1023 => "well-known",
     _    => "other",
 }
}
`HTTP` and `HTTPS` are treated as values to match against, not as variable bindings. Range patterns with constants:
const MIN_AGE: u32 = 18;
const MAX_AGE: u32 = 65;

match age {
 0             => "newborn",
 1..=MIN_AGE   => "minor",
 MIN_AGE..=MAX_AGE => "adult",
 _             => "senior",
}
Constants can appear as range endpoints in `..=` patterns. Associated constants on a struct:
struct Cfg;
impl Cfg {
 const TIMEOUT: u32 = 30;
}

match t {
 Cfg::TIMEOUT => "default",
 1..=10       => "fast",
 _            => "slow",
}
Path syntax (`Cfg::TIMEOUT`) distinguishes this from a binding pattern. Why not just use a variable? A `let` binding in a match arm would shadow the outer variable, not compare against it. Only `const` items, paths, and literals act as value patterns.

What This Unlocks

Key Differences

ConceptOCamlRust
Named constant in match`let module M = struct let x = 5 end` then `M.x` in pattern`const X: i32 = 5;` used directly in `match`
Bare identifier in matchAlways a bindingBare single-segment name: binding; `path::CONST` or `CONST`: value
Associated constantsModule-scoped values`impl Type { const X: T = ...; }`
Range in pattern`when` guard`lo..=hi` range pattern directly in arm
const MIN_AGE: u32 = 18;
const MAX_AGE: u32 = 65;
const HTTP:    u16 = 80;
const HTTPS:   u16 = 443;
const ADMIN:   u16 = 8080;

fn classify_age(age: u32) -> &'static str {
    match age {
        0             => "newborn",
        1..=MIN_AGE   => "minor",
        MIN_AGE..=MAX_AGE => "adult",
        _             => "senior",
    }
}

fn describe_port(p: u16) -> &'static str {
    match p {
        HTTP  => "HTTP",
        HTTPS => "HTTPS",
        ADMIN => "Admin",
        1..=1023      => "well-known",
        1024..=49151  => "registered",
        _             => "dynamic",
    }
}

// Associated consts
struct Cfg;
impl Cfg {
    const TIMEOUT: u32 = 30;
}

fn classify_timeout(t: u32) -> &'static str {
    match t {
        0               => "none",
        Cfg::TIMEOUT    => "default",
        1..=10          => "fast",
        _               => "slow",
    }
}

fn main() {
    for a in [0u32,1,18,40,65,80] { println!("age {}: {}", a, classify_age(a)); }
    for p in [80u16,443,8080,3000,50000] { println!("port {}: {}", p, describe_port(p)); }
    for t in [0u32,5,30,60] { println!("timeout {}: {}", t, classify_timeout(t)); }
}

#[cfg(test)]
mod tests {
    use super::*;
    #[test] fn age18()   { assert_eq!(classify_age(18), "adult"); }
    #[test] fn port80()  { assert_eq!(describe_port(80), "HTTP"); }
    #[test] fn timeout() { assert_eq!(classify_timeout(30), "default"); }
}
(* OCaml: literal constants in patterns *)
let max_age = 65  (* Cannot use this in patterns directly *)

let classify n =
  match n with
  | 0    -> "zero"
  | n when n < 10  -> "small"
  | n when n = 100 -> "century"
  | n when n < 100 -> "medium"
  | _              -> "large"

let classify_char c =
  match c with
  | '\n' -> "newline" | '\t' -> "tab" | ' ' -> "space"
  | c when c>='a'&&c<='z' -> "lower" | _ -> "other"

let () =
  List.iter (fun n -> Printf.printf "%d:%s " n (classify n)) [0;5;50;100;200];
  print_newline ()