matches! Macro
Tutorial Video
Text description (accessibility)
This video demonstrates the "matches! Macro" functional Rust example. Difficulty level: Fundamental. Key concepts covered: Functional Programming. Boolean checks against patterns appear frequently in filters, validations, and conditions. Key difference from OCaml: 1. **Macro vs function**: Rust `matches!` is a procedural macro; OCaml uses higher
Tutorial
The Problem
Boolean checks against patterns appear frequently in filters, validations, and conditions. Before matches!, Rust code needed full match val { Pattern => true, _ => false } expressions for this — verbose for a single-line predicate. The matches! macro (stabilized in Rust 1.42) collapses this to matches!(val, Pattern), supporting or-patterns, guards, and destructuring. It is the most concise way to test whether a value matches a pattern without extracting data.
🎯 Learning Outcomes
matches!(val, Pattern) returns bool without a full match expressionmatches!(val, A | B | C) tests multiple alternativesmatches!(val, Pattern if condition) adds a guardmatches! vs if let vs explicit matchmatches! is most useful: filter, all, any, predicate functionsCode Example
#[derive(Debug)]
enum Status { Active, Inactive, Pending, Banned }
fn is_active(s: &Status) -> bool {
matches!(s, Status::Active)
}
fn is_usable(s: &Status) -> bool {
matches!(s, Status::Active | Status::Pending)
}Key Differences
matches! is a procedural macro; OCaml uses higher-order functions or function shorthand.matches!(val, P if cond) includes a guard; OCaml's function P when cond -> true | _ -> false is the equivalent.matches!(val, A | B) uses Rust or-pattern syntax; OCaml's function A | B -> true is identical in concept.matches! expands to match val { Pattern => true, _ => false } — identical performance to writing it manually.OCaml Approach
OCaml achieves the same with a function:
let is_active = function Status.Active -> true | _ -> false
let is_usable = function Active | Pending -> true | _ -> false
(* or inline: *)
let count_active statuses = List.length (List.filter (function Active -> true | _ -> false) statuses)
Full Source
#![allow(clippy::all)]
//! # `matches!` Macro
//!
//! Test a value against a pattern and get a `bool` — without a full `match` expression.
/// User status enum for demonstration.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Status {
Active,
Inactive,
Pending,
Banned,
}
/// Check if a status is active using matches! macro.
pub fn is_active(status: &Status) -> bool {
matches!(status, Status::Active)
}
/// Check if a status is usable (Active or Pending).
pub fn is_usable(status: &Status) -> bool {
matches!(status, Status::Active | Status::Pending)
}
/// Alternative: traditional match approach (more verbose).
pub fn is_active_match(status: &Status) -> bool {
match status {
Status::Active => true,
_ => false,
}
}
/// Alternative: if-let approach.
pub fn is_active_if_let(status: &Status) -> bool {
if let Status::Active = status {
true
} else {
false
}
}
/// Count active users in a slice.
pub fn count_active(users: &[Status]) -> usize {
users.iter().filter(|u| matches!(u, Status::Active)).count()
}
/// Count usable users (Active or Pending).
pub fn count_usable(users: &[Status]) -> usize {
users
.iter()
.filter(|u| matches!(u, Status::Active | Status::Pending))
.count()
}
/// Filter even numbers that are small (≤ 6) using matches! with guard.
pub fn filter_even_small(nums: &[i32]) -> Vec<i32> {
nums.iter()
.copied()
.filter(|&n| matches!(n, x if x % 2 == 0 && x <= 6))
.collect()
}
/// Alternative without matches! - more verbose.
pub fn filter_even_small_traditional(nums: &[i32]) -> Vec<i32> {
nums.iter()
.copied()
.filter(|&n| n % 2 == 0 && n <= 6)
.collect()
}
/// Shape enum with associated data.
#[derive(Debug, Clone)]
pub enum Shape {
Circle(f64),
Square(f64),
Other,
}
/// Count circles in a collection.
pub fn count_circles(shapes: &[Shape]) -> usize {
shapes
.iter()
.filter(|s| matches!(s, Shape::Circle(_)))
.count()
}
/// Count large shapes (radius/side > 1.0).
pub fn count_large(shapes: &[Shape]) -> usize {
shapes
.iter()
.filter(|s| matches!(s, Shape::Circle(r) | Shape::Square(r) if *r > 1.0))
.count()
}
/// Check if a string matches known Rust keywords.
pub fn is_keyword(word: &str) -> bool {
matches!(
word,
"fn" | "let" | "match" | "if" | "else" | "while" | "for" | "loop"
)
}
/// Filter keywords from a slice of words.
pub fn filter_keywords<'a>(words: &[&'a str]) -> Vec<&'a str> {
words
.iter()
.filter(|&&w| matches!(w, "fn" | "let" | "match" | "if"))
.copied()
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_active() {
assert!(is_active(&Status::Active));
assert!(!is_active(&Status::Inactive));
assert!(!is_active(&Status::Pending));
assert!(!is_active(&Status::Banned));
}
#[test]
fn test_is_usable() {
assert!(is_usable(&Status::Active));
assert!(is_usable(&Status::Pending));
assert!(!is_usable(&Status::Inactive));
assert!(!is_usable(&Status::Banned));
}
#[test]
fn test_approaches_equivalent() {
let statuses = [
Status::Active,
Status::Inactive,
Status::Pending,
Status::Banned,
];
for s in &statuses {
assert_eq!(is_active(s), is_active_match(s));
assert_eq!(is_active(s), is_active_if_let(s));
}
}
#[test]
fn test_count_active() {
let users = [
Status::Active,
Status::Inactive,
Status::Pending,
Status::Banned,
Status::Active,
];
assert_eq!(count_active(&users), 2);
}
#[test]
fn test_count_usable() {
let users = [
Status::Active,
Status::Inactive,
Status::Pending,
Status::Banned,
Status::Active,
];
assert_eq!(count_usable(&users), 3);
}
#[test]
fn test_filter_even_small() {
let nums = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
assert_eq!(filter_even_small(&nums), vec![2, 4, 6]);
assert_eq!(
filter_even_small(&nums),
filter_even_small_traditional(&nums)
);
}
#[test]
fn test_matches_with_guard() {
assert!(matches!(4, x if x % 2 == 0));
assert!(!matches!(3, x if x % 2 == 0));
assert!(matches!(6, x if x % 2 == 0 && x <= 6));
assert!(!matches!(8, x if x % 2 == 0 && x <= 6));
}
#[test]
fn test_shapes() {
let shapes = vec![
Shape::Circle(1.0),
Shape::Square(2.0),
Shape::Other,
Shape::Circle(0.5),
];
assert_eq!(count_circles(&shapes), 2);
assert_eq!(count_large(&shapes), 1); // only Square(2.0)
}
#[test]
fn test_is_keyword() {
assert!(is_keyword("fn"));
assert!(is_keyword("let"));
assert!(is_keyword("match"));
assert!(!is_keyword("hello"));
assert!(!is_keyword("world"));
}
#[test]
fn test_filter_keywords() {
let words = ["fn", "let", "hello", "match", "world"];
assert_eq!(filter_keywords(&words), vec!["fn", "let", "match"]);
}
#[test]
fn test_matches_in_assert() {
let r: Result<i32, &str> = Ok(42);
assert!(matches!(r, Ok(n) if n > 0));
let r2: Result<i32, &str> = Ok(-5);
assert!(!matches!(r2, Ok(n) if n > 0));
let r3: Result<i32, &str> = Err("error");
assert!(!matches!(r3, Ok(_)));
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_is_active() {
assert!(is_active(&Status::Active));
assert!(!is_active(&Status::Inactive));
assert!(!is_active(&Status::Pending));
assert!(!is_active(&Status::Banned));
}
#[test]
fn test_is_usable() {
assert!(is_usable(&Status::Active));
assert!(is_usable(&Status::Pending));
assert!(!is_usable(&Status::Inactive));
assert!(!is_usable(&Status::Banned));
}
#[test]
fn test_approaches_equivalent() {
let statuses = [
Status::Active,
Status::Inactive,
Status::Pending,
Status::Banned,
];
for s in &statuses {
assert_eq!(is_active(s), is_active_match(s));
assert_eq!(is_active(s), is_active_if_let(s));
}
}
#[test]
fn test_count_active() {
let users = [
Status::Active,
Status::Inactive,
Status::Pending,
Status::Banned,
Status::Active,
];
assert_eq!(count_active(&users), 2);
}
#[test]
fn test_count_usable() {
let users = [
Status::Active,
Status::Inactive,
Status::Pending,
Status::Banned,
Status::Active,
];
assert_eq!(count_usable(&users), 3);
}
#[test]
fn test_filter_even_small() {
let nums = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
assert_eq!(filter_even_small(&nums), vec![2, 4, 6]);
assert_eq!(
filter_even_small(&nums),
filter_even_small_traditional(&nums)
);
}
#[test]
fn test_matches_with_guard() {
assert!(matches!(4, x if x % 2 == 0));
assert!(!matches!(3, x if x % 2 == 0));
assert!(matches!(6, x if x % 2 == 0 && x <= 6));
assert!(!matches!(8, x if x % 2 == 0 && x <= 6));
}
#[test]
fn test_shapes() {
let shapes = vec![
Shape::Circle(1.0),
Shape::Square(2.0),
Shape::Other,
Shape::Circle(0.5),
];
assert_eq!(count_circles(&shapes), 2);
assert_eq!(count_large(&shapes), 1); // only Square(2.0)
}
#[test]
fn test_is_keyword() {
assert!(is_keyword("fn"));
assert!(is_keyword("let"));
assert!(is_keyword("match"));
assert!(!is_keyword("hello"));
assert!(!is_keyword("world"));
}
#[test]
fn test_filter_keywords() {
let words = ["fn", "let", "hello", "match", "world"];
assert_eq!(filter_keywords(&words), vec!["fn", "let", "match"]);
}
#[test]
fn test_matches_in_assert() {
let r: Result<i32, &str> = Ok(42);
assert!(matches!(r, Ok(n) if n > 0));
let r2: Result<i32, &str> = Ok(-5);
assert!(!matches!(r2, Ok(n) if n > 0));
let r3: Result<i32, &str> = Err("error");
assert!(!matches!(r3, Ok(_)));
}
}
Deep Comparison
OCaml vs Rust: Pattern Matching to Boolean
Basic Pattern Test
OCaml
type status = Active | Inactive | Pending | Banned
let is_active = function Active -> true | _ -> false
let is_usable = function Active | Pending -> true | _ -> false
Rust
#[derive(Debug)]
enum Status { Active, Inactive, Pending, Banned }
fn is_active(s: &Status) -> bool {
matches!(s, Status::Active)
}
fn is_usable(s: &Status) -> bool {
matches!(s, Status::Active | Status::Pending)
}
In Filter Predicates
OCaml
let active_count =
users |> List.filter (function Active -> true | _ -> false) |> List.length
Rust
let active_count = users.iter()
.filter(|u| matches!(u, Status::Active))
.count();
With Guards
OCaml
let is_even_small x = match x with
| n when n mod 2 = 0 && n <= 6 -> true
| _ -> false
Rust
fn is_even_small(x: i32) -> bool {
matches!(x, n if n % 2 == 0 && n <= 6)
}
Key Differences
| Aspect | OCaml | Rust |
|---|---|---|
| Syntax | function P -> true \| _ -> false | matches!(val, P) |
| Verbosity | Requires explicit true/false arms | Single macro call |
| OR patterns | P1 \| P2 -> true | matches!(v, P1 \| P2) |
| Guards | when condition | if condition |
| Common in | Less common (full match preferred) | Very common in filter/assert |
Macro Expansion
matches!(x, Pattern) expands to:
match x {
Pattern => true,
_ => false,
}
No runtime overhead — purely syntactic convenience.
Exercises
fn is_success(code: u16) -> bool using matches!(code, 200..=299) and use it to filter a list of response codes.fn is_valid_move(event: &Event) -> bool using matches!(event, Event::Move { x, y } if *x >= 0 && *y >= 0).fn all_active(statuses: &[Status]) -> bool and fn any_banned(statuses: &[Status]) -> bool using matches! inside iter().all() and iter().any().