ExamplesBy LevelBy TopicLearning Paths
569 Intermediate

Range Patterns

Functional Programming

Tutorial Video

Text description (accessibility)

This video demonstrates the "Range Patterns" functional Rust example. Difficulty level: Intermediate. Key concepts covered: Functional Programming. Many real-world conditions are naturally expressed as ranges: grade thresholds, age categories, ASCII character classification, HTTP status code groups. Key difference from OCaml: 1. **Pattern vs guard**: Rust has first

Tutorial

The Problem

Many real-world conditions are naturally expressed as ranges: grade thresholds, age categories, ASCII character classification, HTTP status code groups. Without range patterns, these require chains of if/else if with repeated comparisons. Range patterns (lo..=hi) in match arms express these conditions declaratively, with the added benefit of exhaustiveness checking across the covered domain. Range patterns are used in compilers, ASCII processors, game scoring systems, and any domain with numeric thresholds.

🎯 Learning Outcomes

  • • How 90..=100 => 'A' matches any value in the inclusive range
  • • How range patterns work for both integer and character values
  • • How ranges compose with or-patterns, guards, and bindings
  • • How the compiler checks for overlapping or missing ranges
  • • Where range patterns are more readable than equivalent if-else chains
  • Code Example

    #![allow(clippy::all)]
    //! Range Patterns
    //!
    //! Matching ranges with ..= syntax.
    
    /// Match numeric ranges.
    pub fn grade(score: u32) -> char {
        match score {
            90..=100 => 'A',
            80..=89 => 'B',
            70..=79 => 'C',
            60..=69 => 'D',
            _ => 'F',
        }
    }
    
    /// Match character ranges.
    pub fn char_type(c: char) -> &'static str {
        match c {
            'a'..='z' => "lowercase",
            'A'..='Z' => "uppercase",
            '0'..='9' => "digit",
            _ => "other",
        }
    }
    
    /// Inclusive range with guard.
    pub fn categorize(n: i32) -> &'static str {
        match n {
            i32::MIN..=-1 => "negative",
            0 => "zero",
            1..=100 => "small positive",
            101..=i32::MAX => "large positive",
        }
    }
    
    /// Range in struct field.
    pub struct Temperature(pub i32);
    
    pub fn temp_status(t: &Temperature) -> &'static str {
        match t.0 {
            ..=-10 => "freezing",
            -9..=0 => "cold",
            1..=20 => "cool",
            21..=30 => "warm",
            31.. => "hot",
        }
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_grade() {
            assert_eq!(grade(95), 'A');
            assert_eq!(grade(85), 'B');
            assert_eq!(grade(55), 'F');
        }
    
        #[test]
        fn test_char_type() {
            assert_eq!(char_type('a'), "lowercase");
            assert_eq!(char_type('Z'), "uppercase");
            assert_eq!(char_type('5'), "digit");
        }
    
        #[test]
        fn test_categorize() {
            assert_eq!(categorize(-5), "negative");
            assert_eq!(categorize(0), "zero");
            assert_eq!(categorize(50), "small positive");
        }
    
        #[test]
        fn test_temp() {
            assert_eq!(temp_status(&Temperature(-20)), "freezing");
            assert_eq!(temp_status(&Temperature(25)), "warm");
        }
    }

    Key Differences

  • Pattern vs guard: Rust has first-class range patterns; OCaml requires when guards for ranges — a fundamental syntactic difference.
  • Char ranges: Rust 'a'..='z' is a pattern; OCaml handles this with | c when Char.code c >= 97 && Char.code c <= 122.
  • Exhaustiveness: Rust can verify that numeric range patterns cover the full domain; OCaml cannot do exhaustiveness checking for guarded arms.
  • Exclusive ranges: Rust's exclusive .. is not yet stable in patterns (as of 2024); OCaml has no range patterns at all.
  • OCaml Approach

    OCaml does not have range patterns directly — numeric ranges use guards:

    let grade score = match score with
      | s when s >= 90 -> 'A'
      | s when s >= 80 -> 'B'
      | s when s >= 70 -> 'C'
      | _ -> 'F'
    

    Character ranges use the same guard approach or a Char.code comparison.

    Full Source

    #![allow(clippy::all)]
    //! Range Patterns
    //!
    //! Matching ranges with ..= syntax.
    
    /// Match numeric ranges.
    pub fn grade(score: u32) -> char {
        match score {
            90..=100 => 'A',
            80..=89 => 'B',
            70..=79 => 'C',
            60..=69 => 'D',
            _ => 'F',
        }
    }
    
    /// Match character ranges.
    pub fn char_type(c: char) -> &'static str {
        match c {
            'a'..='z' => "lowercase",
            'A'..='Z' => "uppercase",
            '0'..='9' => "digit",
            _ => "other",
        }
    }
    
    /// Inclusive range with guard.
    pub fn categorize(n: i32) -> &'static str {
        match n {
            i32::MIN..=-1 => "negative",
            0 => "zero",
            1..=100 => "small positive",
            101..=i32::MAX => "large positive",
        }
    }
    
    /// Range in struct field.
    pub struct Temperature(pub i32);
    
    pub fn temp_status(t: &Temperature) -> &'static str {
        match t.0 {
            ..=-10 => "freezing",
            -9..=0 => "cold",
            1..=20 => "cool",
            21..=30 => "warm",
            31.. => "hot",
        }
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_grade() {
            assert_eq!(grade(95), 'A');
            assert_eq!(grade(85), 'B');
            assert_eq!(grade(55), 'F');
        }
    
        #[test]
        fn test_char_type() {
            assert_eq!(char_type('a'), "lowercase");
            assert_eq!(char_type('Z'), "uppercase");
            assert_eq!(char_type('5'), "digit");
        }
    
        #[test]
        fn test_categorize() {
            assert_eq!(categorize(-5), "negative");
            assert_eq!(categorize(0), "zero");
            assert_eq!(categorize(50), "small positive");
        }
    
        #[test]
        fn test_temp() {
            assert_eq!(temp_status(&Temperature(-20)), "freezing");
            assert_eq!(temp_status(&Temperature(25)), "warm");
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_grade() {
            assert_eq!(grade(95), 'A');
            assert_eq!(grade(85), 'B');
            assert_eq!(grade(55), 'F');
        }
    
        #[test]
        fn test_char_type() {
            assert_eq!(char_type('a'), "lowercase");
            assert_eq!(char_type('Z'), "uppercase");
            assert_eq!(char_type('5'), "digit");
        }
    
        #[test]
        fn test_categorize() {
            assert_eq!(categorize(-5), "negative");
            assert_eq!(categorize(0), "zero");
            assert_eq!(categorize(50), "small positive");
        }
    
        #[test]
        fn test_temp() {
            assert_eq!(temp_status(&Temperature(-20)), "freezing");
            assert_eq!(temp_status(&Temperature(25)), "warm");
        }
    }

    Deep Comparison

    OCaml vs Rust: pattern range

    See example.rs and example.ml for implementations.

    Exercises

  • Temperature zones: Implement fn climate_zone(temp_c: i32) -> &'static str using range patterns to classify as "arctic" (below -20), "cold" (-20..=0), "temperate" (1..=20), "hot" (21..=40), "extreme" (above 40).
  • ASCII classifier: Write fn ascii_category(c: u8) -> &'static str using range patterns to classify bytes as control (0..=31), printable (32..=126), or extended (127..=255).
  • Combined ranges: Implement fn is_weekend_hour(hour: u8) -> bool using 0..=8 | 20..=23 or-with-range to identify typical non-business hours.
  • Open Source Repos