ExamplesBy LevelBy TopicLearning Paths
582 Fundamental

Tuple Pattern Matching

Functional Programming

Tutorial Video

Text description (accessibility)

This video demonstrates the "Tuple Pattern Matching" functional Rust example. Difficulty level: Fundamental. Key concepts covered: Functional Programming. Many decisions depend on the combination of multiple conditions. Key difference from OCaml: 1. **Syntax**: Rust `match (a, b)` and OCaml `match (a, b) with` are nearly identical — the pattern is universal across ML

Tutorial

The Problem

Many decisions depend on the combination of multiple conditions. FizzBuzz is the canonical example: the output depends on two independent Boolean conditions. Without tuple matching, you need nested if statements. Matching on a tuple (cond1, cond2) expresses the decision matrix declaratively: each arm covers exactly one combination. This pattern is used in state transition tables, game logic, protocol state machines, and any logic where multiple independent conditions determine the outcome.

🎯 Learning Outcomes

  • • How match (a, b) { (true, true) => ... } matches all combinations of two conditions
  • • How tuple patterns scale to three or more conditions
  • • How to combine tuples with other patterns: match (opt_a, opt_b) { (Some(a), Some(b)) => ... }
  • • How _ in tuple positions allows partial matching: (true, _) => ...
  • • Where tuple matching replaces nested if/else: FizzBuzz, state transitions, validation matrices
  • Code Example

    fn fizzbuzz(n: u32) -> String {
        match (n % 3 == 0, n % 5 == 0) {
            (true, true)   => "FizzBuzz".into(),
            (true, false)  => "Fizz".into(),
            (false, true)  => "Buzz".into(),
            (false, false) => n.to_string(),
        }
    }

    Key Differences

  • Syntax: Rust match (a, b) and OCaml match (a, b) with are nearly identical — the pattern is universal across ML-family languages.
  • Decision matrix readability: Both languages make the decision matrix explicit and self-documenting; imperative if/else chains obscure the structure.
  • Exhaustiveness: Both compilers verify all (bool, bool) combinations are covered — adding a case for (_, _) or relying on exhaustiveness is clear.
  • Tuple creation cost: Rust tuples are stack-allocated — the (a, b) expression has zero heap overhead; OCaml tuples are heap-allocated GC values.
  • OCaml Approach

    OCaml tuple pattern matching is identical:

    let fizzbuzz n = match (n mod 3 = 0, n mod 5 = 0) with
      | (true, true) -> "FizzBuzz"
      | (true, false) -> "Fizz"
      | (false, true) -> "Buzz"
      | (false, false) -> string_of_int n
    

    This is one of the most natural examples of OCaml pattern matching — the code reads exactly like a truth table.

    Full Source

    #![allow(clippy::all)]
    //! # Tuple Pattern Matching
    //!
    //! Match on multiple values simultaneously using tuple patterns.
    
    /// FizzBuzz using tuple pattern matching.
    pub fn fizzbuzz(n: u32) -> String {
        match (n % 3 == 0, n % 5 == 0) {
            (true, true) => "FizzBuzz".into(),
            (true, false) => "Fizz".into(),
            (false, true) => "Buzz".into(),
            (false, false) => n.to_string(),
        }
    }
    
    /// Alternative using if-else chain.
    pub fn fizzbuzz_if(n: u32) -> String {
        if n % 3 == 0 && n % 5 == 0 {
            "FizzBuzz".into()
        } else if n % 3 == 0 {
            "Fizz".into()
        } else if n % 5 == 0 {
            "Buzz".into()
        } else {
            n.to_string()
        }
    }
    
    /// Traffic light state.
    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    pub enum Light {
        Red,
        Yellow,
        Green,
    }
    
    /// State machine for traffic light with emergency override.
    pub fn next_light(light: Light, emergency: bool) -> Light {
        match (light, emergency) {
            (_, true) => Light::Red,
            (Light::Red, false) => Light::Green,
            (Light::Green, false) => Light::Yellow,
            (Light::Yellow, false) => Light::Red,
        }
    }
    
    /// Compare two values and return ordering description.
    pub fn compare(a: i32, b: i32) -> &'static str {
        match (a > b, a < b) {
            (true, false) => "greater",
            (false, true) => "less",
            _ => "equal",
        }
    }
    
    /// Alternative using Ordering.
    pub fn compare_ord(a: i32, b: i32) -> &'static str {
        match a.cmp(&b) {
            std::cmp::Ordering::Greater => "greater",
            std::cmp::Ordering::Less => "less",
            std::cmp::Ordering::Equal => "equal",
        }
    }
    
    /// Match on three boolean conditions.
    pub fn classify_triple(a: bool, b: bool, c: bool) -> &'static str {
        match (a, b, c) {
            (true, true, true) => "all true",
            (false, false, false) => "all false",
            (true, _, _) => "a is true",
            (_, true, _) => "b is true",
            (_, _, true) => "c is true",
            _ => "unreachable",
        }
    }
    
    /// Point classification using tuple matching.
    pub fn quadrant(x: i32, y: i32) -> &'static str {
        match (x.signum(), y.signum()) {
            (1, 1) => "Q1",
            (-1, 1) => "Q2",
            (-1, -1) => "Q3",
            (1, -1) => "Q4",
            (0, _) | (_, 0) => "axis",
            _ => "origin",
        }
    }
    
    /// Match on Option pair.
    pub fn both_some<T, U>(a: Option<T>, b: Option<U>) -> bool {
        matches!((a, b), (Some(_), Some(_)))
    }
    
    /// Extract values from Option pair.
    pub fn extract_pair<T, U>(a: Option<T>, b: Option<U>) -> Option<(T, U)> {
        match (a, b) {
            (Some(x), Some(y)) => Some((x, y)),
            _ => None,
        }
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_fizzbuzz() {
            assert_eq!(fizzbuzz(1), "1");
            assert_eq!(fizzbuzz(3), "Fizz");
            assert_eq!(fizzbuzz(5), "Buzz");
            assert_eq!(fizzbuzz(15), "FizzBuzz");
            assert_eq!(fizzbuzz(7), "7");
        }
    
        #[test]
        fn test_fizzbuzz_approaches_equivalent() {
            for n in 1..=30 {
                assert_eq!(fizzbuzz(n), fizzbuzz_if(n));
            }
        }
    
        #[test]
        fn test_next_light_normal() {
            assert_eq!(next_light(Light::Red, false), Light::Green);
            assert_eq!(next_light(Light::Green, false), Light::Yellow);
            assert_eq!(next_light(Light::Yellow, false), Light::Red);
        }
    
        #[test]
        fn test_next_light_emergency() {
            assert_eq!(next_light(Light::Red, true), Light::Red);
            assert_eq!(next_light(Light::Green, true), Light::Red);
            assert_eq!(next_light(Light::Yellow, true), Light::Red);
        }
    
        #[test]
        fn test_compare() {
            assert_eq!(compare(5, 3), "greater");
            assert_eq!(compare(3, 5), "less");
            assert_eq!(compare(4, 4), "equal");
        }
    
        #[test]
        fn test_compare_approaches_equivalent() {
            let cases = [(1, 2), (2, 1), (3, 3), (-1, 1), (0, 0)];
            for (a, b) in cases {
                assert_eq!(compare(a, b), compare_ord(a, b));
            }
        }
    
        #[test]
        fn test_classify_triple() {
            assert_eq!(classify_triple(true, true, true), "all true");
            assert_eq!(classify_triple(false, false, false), "all false");
            assert_eq!(classify_triple(true, false, false), "a is true");
            assert_eq!(classify_triple(false, true, false), "b is true");
            assert_eq!(classify_triple(false, false, true), "c is true");
        }
    
        #[test]
        fn test_quadrant() {
            assert_eq!(quadrant(1, 1), "Q1");
            assert_eq!(quadrant(-1, 1), "Q2");
            assert_eq!(quadrant(-1, -1), "Q3");
            assert_eq!(quadrant(1, -1), "Q4");
            assert_eq!(quadrant(0, 5), "axis");
            assert_eq!(quadrant(5, 0), "axis");
        }
    
        #[test]
        fn test_both_some() {
            assert!(both_some(Some(1), Some(2)));
            assert!(!both_some(Some(1), None::<i32>));
            assert!(!both_some(None::<i32>, Some(2)));
            assert!(!both_some(None::<i32>, None::<i32>));
        }
    
        #[test]
        fn test_extract_pair() {
            assert_eq!(extract_pair(Some(1), Some("a")), Some((1, "a")));
            assert_eq!(extract_pair(Some(1), None::<&str>), None);
            assert_eq!(extract_pair(None::<i32>, Some("a")), None);
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_fizzbuzz() {
            assert_eq!(fizzbuzz(1), "1");
            assert_eq!(fizzbuzz(3), "Fizz");
            assert_eq!(fizzbuzz(5), "Buzz");
            assert_eq!(fizzbuzz(15), "FizzBuzz");
            assert_eq!(fizzbuzz(7), "7");
        }
    
        #[test]
        fn test_fizzbuzz_approaches_equivalent() {
            for n in 1..=30 {
                assert_eq!(fizzbuzz(n), fizzbuzz_if(n));
            }
        }
    
        #[test]
        fn test_next_light_normal() {
            assert_eq!(next_light(Light::Red, false), Light::Green);
            assert_eq!(next_light(Light::Green, false), Light::Yellow);
            assert_eq!(next_light(Light::Yellow, false), Light::Red);
        }
    
        #[test]
        fn test_next_light_emergency() {
            assert_eq!(next_light(Light::Red, true), Light::Red);
            assert_eq!(next_light(Light::Green, true), Light::Red);
            assert_eq!(next_light(Light::Yellow, true), Light::Red);
        }
    
        #[test]
        fn test_compare() {
            assert_eq!(compare(5, 3), "greater");
            assert_eq!(compare(3, 5), "less");
            assert_eq!(compare(4, 4), "equal");
        }
    
        #[test]
        fn test_compare_approaches_equivalent() {
            let cases = [(1, 2), (2, 1), (3, 3), (-1, 1), (0, 0)];
            for (a, b) in cases {
                assert_eq!(compare(a, b), compare_ord(a, b));
            }
        }
    
        #[test]
        fn test_classify_triple() {
            assert_eq!(classify_triple(true, true, true), "all true");
            assert_eq!(classify_triple(false, false, false), "all false");
            assert_eq!(classify_triple(true, false, false), "a is true");
            assert_eq!(classify_triple(false, true, false), "b is true");
            assert_eq!(classify_triple(false, false, true), "c is true");
        }
    
        #[test]
        fn test_quadrant() {
            assert_eq!(quadrant(1, 1), "Q1");
            assert_eq!(quadrant(-1, 1), "Q2");
            assert_eq!(quadrant(-1, -1), "Q3");
            assert_eq!(quadrant(1, -1), "Q4");
            assert_eq!(quadrant(0, 5), "axis");
            assert_eq!(quadrant(5, 0), "axis");
        }
    
        #[test]
        fn test_both_some() {
            assert!(both_some(Some(1), Some(2)));
            assert!(!both_some(Some(1), None::<i32>));
            assert!(!both_some(None::<i32>, Some(2)));
            assert!(!both_some(None::<i32>, None::<i32>));
        }
    
        #[test]
        fn test_extract_pair() {
            assert_eq!(extract_pair(Some(1), Some("a")), Some((1, "a")));
            assert_eq!(extract_pair(Some(1), None::<&str>), None);
            assert_eq!(extract_pair(None::<i32>, Some("a")), None);
        }
    }

    Deep Comparison

    OCaml vs Rust: Tuple Pattern Matching

    FizzBuzz Example

    OCaml

    let fizzbuzz n = match (n mod 3 = 0, n mod 5 = 0) with
      | (true, true)   -> "FizzBuzz"
      | (true, false)  -> "Fizz"
      | (false, true)  -> "Buzz"
      | (false, false) -> string_of_int n
    

    Rust

    fn fizzbuzz(n: u32) -> String {
        match (n % 3 == 0, n % 5 == 0) {
            (true, true)   => "FizzBuzz".into(),
            (true, false)  => "Fizz".into(),
            (false, true)  => "Buzz".into(),
            (false, false) => n.to_string(),
        }
    }
    

    State Machine with Tuple

    OCaml

    type light = Red | Yellow | Green
    
    let next (l, emergency) = match (l, emergency) with
      | (_, true)        -> Red
      | (Red, false)     -> Green
      | (Green, false)   -> Yellow
      | (Yellow, false)  -> Red
    

    Rust

    fn next_light(light: Light, emergency: bool) -> Light {
        match (light, emergency) {
            (_, true)             => Light::Red,
            (Light::Red, false)   => Light::Green,
            (Light::Green, false) => Light::Yellow,
            (Light::Yellow, false)=> Light::Red,
        }
    }
    

    Key Differences

    AspectOCamlRust
    Syntaxmatch (a, b) withmatch (a, b) { }
    Wildcard__
    Arrow->=>
    Pattern guardswhen conditionif condition

    Benefits of Tuple Matching

  • Multi-value decisions - Match on combinations without nested if/else
  • State machines - Clear representation of (state, input) → new_state
  • Exhaustiveness - Compiler ensures all combinations are handled
  • Readability - Truth table style patterns
  • Exercises

  • Three-way FizzBuzz: Extend FizzBuzz to also handle multiples of 7 as "Bazz" — match on (n%3==0, n%5==0, n%7==0) and use _ to collapse irrelevant combinations.
  • State + event matrix: Implement fn transition(state: State, event: Event) -> State for a traffic light using tuple matching on (state, event).
  • Option matrix: Write fn combine(a: Option<i32>, b: Option<i32>) -> Option<i32> that returns Some(a + b) if both are Some, Some(a) if only a, Some(b) if only b, None otherwise.
  • Open Source Repos