Enum Variant Patterns
Tutorial Video
Text description (accessibility)
This video demonstrates the "Enum Variant Patterns" functional Rust example. Difficulty level: Fundamental. Key concepts covered: Functional Programming. Algebraic data types (ADTs) with sum-type variants are the backbone of functional programming. Key difference from OCaml: 1. **Unit variants**: Rust `Message::Quit` requires the enum name; OCaml `Quit` can be used unqualified in scope.
Tutorial
The Problem
Algebraic data types (ADTs) with sum-type variants are the backbone of functional programming. Enums in Rust are full ADTs: variants can carry no data (unit), named fields (struct-like), or positional fields (tuple-like). Pattern matching on enum variants is exhaustive — the compiler verifies every variant is handled. This makes enums with match the preferred tool for command dispatch, protocol parsing, event handling, and any domain with a finite set of cases. It is the functional alternative to class hierarchies with virtual dispatch.
🎯 Learning Outcomes
if let provides non-exhaustive matching for a single variant of interestmatches! tests variant membership without extracting dataCode Example
#![allow(clippy::all)]
//! Enum Variant Patterns
//!
//! Matching and destructuring enum variants.
#[derive(Debug, PartialEq)]
pub enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(u8, u8, u8),
}
/// Match all variants.
pub fn process_message(msg: &Message) -> String {
match msg {
Message::Quit => "Quit".to_string(),
Message::Move { x, y } => format!("Move to ({}, {})", x, y),
Message::Write(text) => format!("Write: {}", text),
Message::ChangeColor(r, g, b) => format!("Color: rgb({}, {}, {})", r, g, b),
}
}
/// if let for single variant.
pub fn is_quit(msg: &Message) -> bool {
matches!(msg, Message::Quit)
}
/// Extract from specific variant.
pub fn get_write_text(msg: &Message) -> Option<&str> {
if let Message::Write(text) = msg {
Some(text)
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_process() {
assert_eq!(process_message(&Message::Quit), "Quit");
assert!(process_message(&Message::Move { x: 1, y: 2 }).contains("1"));
assert!(process_message(&Message::Write("hi".into())).contains("hi"));
}
#[test]
fn test_is_quit() {
assert!(is_quit(&Message::Quit));
assert!(!is_quit(&Message::Write("x".into())));
}
#[test]
fn test_get_write() {
assert_eq!(
get_write_text(&Message::Write("hello".into())),
Some("hello")
);
assert_eq!(get_write_text(&Message::Quit), None);
}
}Key Differences
Message::Quit requires the enum name; OCaml Quit can be used unqualified in scope.Move { x, y } and OCaml inline records Move of {x: int; y: int} are equivalent.matches! vs boolean match**: Rust's matches! macro; OCaml achieves the same with (= Quit) or a function Quit -> true | _ -> false.OCaml Approach
OCaml's variant types are the direct equivalent:
type message = Quit | Move of {x: int; y: int} | Write of string | ChangeColor of int * int * int
let process = function
| Quit -> "Quit"
| Move {x; y} -> Printf.sprintf "Move to (%d, %d)" x y
| Write text -> "Write: " ^ text
| ChangeColor (r, g, b) -> Printf.sprintf "Color: rgb(%d, %d, %d)" r g b
Full Source
#![allow(clippy::all)]
//! Enum Variant Patterns
//!
//! Matching and destructuring enum variants.
#[derive(Debug, PartialEq)]
pub enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(u8, u8, u8),
}
/// Match all variants.
pub fn process_message(msg: &Message) -> String {
match msg {
Message::Quit => "Quit".to_string(),
Message::Move { x, y } => format!("Move to ({}, {})", x, y),
Message::Write(text) => format!("Write: {}", text),
Message::ChangeColor(r, g, b) => format!("Color: rgb({}, {}, {})", r, g, b),
}
}
/// if let for single variant.
pub fn is_quit(msg: &Message) -> bool {
matches!(msg, Message::Quit)
}
/// Extract from specific variant.
pub fn get_write_text(msg: &Message) -> Option<&str> {
if let Message::Write(text) = msg {
Some(text)
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_process() {
assert_eq!(process_message(&Message::Quit), "Quit");
assert!(process_message(&Message::Move { x: 1, y: 2 }).contains("1"));
assert!(process_message(&Message::Write("hi".into())).contains("hi"));
}
#[test]
fn test_is_quit() {
assert!(is_quit(&Message::Quit));
assert!(!is_quit(&Message::Write("x".into())));
}
#[test]
fn test_get_write() {
assert_eq!(
get_write_text(&Message::Write("hello".into())),
Some("hello")
);
assert_eq!(get_write_text(&Message::Quit), None);
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_process() {
assert_eq!(process_message(&Message::Quit), "Quit");
assert!(process_message(&Message::Move { x: 1, y: 2 }).contains("1"));
assert!(process_message(&Message::Write("hi".into())).contains("hi"));
}
#[test]
fn test_is_quit() {
assert!(is_quit(&Message::Quit));
assert!(!is_quit(&Message::Write("x".into())));
}
#[test]
fn test_get_write() {
assert_eq!(
get_write_text(&Message::Write("hello".into())),
Some("hello")
);
assert_eq!(get_write_text(&Message::Quit), None);
}
}
Deep Comparison
OCaml vs Rust: pattern enum variants
See example.rs and example.ml for implementations.
Exercises
Command enum with Move(Direction), Look, Take(String), Drop(String), Quit and write fn execute(cmd: Command, world: &mut World) -> String.JsonValue enum covering Null, Bool(bool), Number(f64), Str(String), Array(Vec<JsonValue>), Object(Vec<(String, JsonValue)>) with a display function.fn parse_message(s: &str) -> Option<Message> that parses "quit", "move X Y", "write TEXT", "color R G B" into the Message enum.