Range Patterns
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
90..=100 => 'A' matches any value in the inclusive rangeCode 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
when guards for ranges — a fundamental syntactic difference.'a'..='z' is a pattern; OCaml handles this with | c when Char.code c >= 97 && Char.code c <= 122... 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");
}
}#[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
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).fn ascii_category(c: u8) -> &'static str using range patterns to classify bytes as control (0..=31), printable (32..=126), or extended (127..=255).fn is_weekend_hour(hour: u8) -> bool using 0..=8 | 20..=23 or-with-range to identify typical non-business hours.