Pattern Guards
Tutorial Video
Text description (accessibility)
This video demonstrates the "Pattern Guards" functional Rust example. Difficulty level: Fundamental. Key concepts covered: Functional Programming. Patterns alone cannot express all matching conditions — sometimes you need to test an arbitrary Boolean expression in addition to the structural match. Key difference from OCaml: 1. **Keyword**: Rust uses `if` in guards (`arm if cond`); OCaml uses `when` (`arm when cond`).
Tutorial
The Problem
Patterns alone cannot express all matching conditions — sometimes you need to test an arbitrary Boolean expression in addition to the structural match. Pattern guards (if condition after a match arm pattern) fill this gap: they allow any expression as an additional condition, while keeping the pattern for structural decomposition. Guards are used heavily in parsers, compilers, and game logic where the same structural form can have different meanings depending on computed values.
🎯 Learning Outcomes
match n { x if x < 0 => ... } combines a binding with a Boolean guard(x, y) if x == yCode Example
#![allow(clippy::all)]
//! Pattern Guards
//!
//! Additional conditions with if in match arms.
/// Guard with condition.
pub fn categorize(n: i32) -> &'static str {
match n {
x if x < 0 => "negative",
x if x == 0 => "zero",
x if x < 10 => "small positive",
_ => "large positive",
}
}
/// Guard with multiple conditions.
pub fn check_range(n: i32, min: i32, max: i32) -> bool {
match n {
x if x >= min && x <= max => true,
_ => false,
}
}
/// Guard with destructuring.
pub fn process_point(point: (i32, i32)) -> &'static str {
match point {
(0, 0) => "origin",
(x, y) if x == y => "diagonal",
(x, _) if x > 0 => "positive x",
(_, y) if y > 0 => "positive y",
_ => "other",
}
}
/// Guard with Option.
pub fn check_option(opt: Option<i32>) -> &'static str {
match opt {
Some(x) if x > 100 => "large",
Some(x) if x > 0 => "positive",
Some(0) => "zero",
Some(_) => "negative",
None => "none",
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_categorize() {
assert_eq!(categorize(-5), "negative");
assert_eq!(categorize(0), "zero");
assert_eq!(categorize(5), "small positive");
assert_eq!(categorize(100), "large positive");
}
#[test]
fn test_range() {
assert!(check_range(5, 1, 10));
assert!(!check_range(15, 1, 10));
}
#[test]
fn test_point() {
assert_eq!(process_point((0, 0)), "origin");
assert_eq!(process_point((5, 5)), "diagonal");
}
#[test]
fn test_option() {
assert_eq!(check_option(Some(200)), "large");
assert_eq!(check_option(None), "none");
}
}Key Differences
if in guards (arm if cond); OCaml uses when (arm when cond).OCaml Approach
OCaml uses when as its guard keyword:
let categorize n = match n with
| x when x < 0 -> "negative"
| 0 -> "zero"
| x when x < 10 -> "small positive"
| _ -> "large positive"
The semantics are identical to Rust's if guards.
Full Source
#![allow(clippy::all)]
//! Pattern Guards
//!
//! Additional conditions with if in match arms.
/// Guard with condition.
pub fn categorize(n: i32) -> &'static str {
match n {
x if x < 0 => "negative",
x if x == 0 => "zero",
x if x < 10 => "small positive",
_ => "large positive",
}
}
/// Guard with multiple conditions.
pub fn check_range(n: i32, min: i32, max: i32) -> bool {
match n {
x if x >= min && x <= max => true,
_ => false,
}
}
/// Guard with destructuring.
pub fn process_point(point: (i32, i32)) -> &'static str {
match point {
(0, 0) => "origin",
(x, y) if x == y => "diagonal",
(x, _) if x > 0 => "positive x",
(_, y) if y > 0 => "positive y",
_ => "other",
}
}
/// Guard with Option.
pub fn check_option(opt: Option<i32>) -> &'static str {
match opt {
Some(x) if x > 100 => "large",
Some(x) if x > 0 => "positive",
Some(0) => "zero",
Some(_) => "negative",
None => "none",
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_categorize() {
assert_eq!(categorize(-5), "negative");
assert_eq!(categorize(0), "zero");
assert_eq!(categorize(5), "small positive");
assert_eq!(categorize(100), "large positive");
}
#[test]
fn test_range() {
assert!(check_range(5, 1, 10));
assert!(!check_range(15, 1, 10));
}
#[test]
fn test_point() {
assert_eq!(process_point((0, 0)), "origin");
assert_eq!(process_point((5, 5)), "diagonal");
}
#[test]
fn test_option() {
assert_eq!(check_option(Some(200)), "large");
assert_eq!(check_option(None), "none");
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_categorize() {
assert_eq!(categorize(-5), "negative");
assert_eq!(categorize(0), "zero");
assert_eq!(categorize(5), "small positive");
assert_eq!(categorize(100), "large positive");
}
#[test]
fn test_range() {
assert!(check_range(5, 1, 10));
assert!(!check_range(15, 1, 10));
}
#[test]
fn test_point() {
assert_eq!(process_point((0, 0)), "origin");
assert_eq!(process_point((5, 5)), "diagonal");
}
#[test]
fn test_option() {
assert_eq!(check_option(Some(200)), "large");
assert_eq!(check_option(None), "none");
}
}
Deep Comparison
OCaml vs Rust: pattern guards
See example.rs and example.ml for implementations.
Exercises
match n { n if n % 15 == 0 => "FizzBuzz", ... } without using if/else.fn classify_point(x: f64, y: f64) -> &'static str using nested match with guards to distinguish origin, axes, quadrants, and far points (distance > 10).Temperature enum with Celsius(f64) and Fahrenheit(f64) and use guards to classify as "freezing", "cold", "warm", "hot" across both variants.