Complete Guide to Rust Match

Introduction to Match in Rust

The match expression is one of Rust's most powerful features. It allows you to compare a value against a series of patterns and execute code based on which pattern matches. Think of it as a supercharged switch statement from C-like languages, but with much more flexibility and safety.

Key Concepts

  • Exhaustive: Must cover all possible cases
  • Pattern Matching: Can match on values, structures, ranges, and more
  • Destructuring: Can break apart complex data types
  • Expression-based: Returns a value
  • Zero-cost: Compiled to efficient branch tables

1. Basic Match Syntax

Simple Match Expressions

fn main() {
let number = 3;
match number {
1 => println!("It's one!"),
2 => println!("It's two!"),
3 => println!("It's three!"),
_ => println!("It's something else!"),
}
// Match as an expression (returns value)
let description = match number {
1 => "one",
2 => "two",
3 => "three",
_ => "other",
};
println!("The number is {}", description);
// Match with multiple lines in arms
let result = match number {
1 => {
println!("Processing one...");
"one"
}
2 => {
println!("Processing two...");
"two"
}
_ => {
println!("Processing other...");
"other"
}
};
println!("Result: {}", result);
}

The Wildcard Pattern _

fn main() {
let number = 10;
match number {
1 => println!("One"),
2 => println!("Two"),
3 => println!("Three"),
_ => println!("Anything else"), // Catches all other values
}
// Using _ when you don't need the value
let optional: Option<i32> = Some(42);
match optional {
Some(_) => println!("Got a value, but don't care what it is"),
None => println!("Got nothing"),
}
// Ignoring parts of a tuple
let tuple = (10, 20, 30);
match tuple {
(x, _, z) => println!("x: {}, z: {}", x, z),
}
}

2. Matching on Enums

Basic Enum Matching

enum Coin {
Penny,
Nickel,
Dime,
Quarter,
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => 1,
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter => 25,
}
}
// Enum with data
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
fn process_message(msg: Message) {
match msg {
Message::Quit => println!("Quitting..."),
Message::Move { x, y } => println!("Moving to ({}, {})", x, y),
Message::Write(text) => println!("Writing: {}", text),
Message::ChangeColor(r, g, b) => {
println!("Changing color to RGB({}, {}, {})", r, g, b);
}
}
}
fn main() {
let penny = Coin::Penny;
println!("Penny value: {} cents", value_in_cents(penny));
process_message(Message::Quit);
process_message(Message::Move { x: 10, y: 20 });
process_message(Message::Write(String::from("Hello")));
process_message(Message::ChangeColor(255, 0, 0));
}

Matching Option

fn main() {
let some_value: Option<i32> = Some(42);
let no_value: Option<i32> = None;
// Basic Option matching
match some_value {
Some(x) => println!("Got a value: {}", x),
None => println!("Got nothing"),
}
// Option in calculations
fn double_value(x: Option<i32>) -> Option<i32> {
match x {
Some(val) => Some(val * 2),
None => None,
}
}
let doubled = double_value(some_value);
println!("Doubled: {:?}", doubled);
// Nested Option matching
let nested: Option<Option<i32>> = Some(Some(42));
match nested {
Some(Some(x)) => println!("Deep value: {}", x),
Some(None) => println!("Outer Some, inner None"),
None => println!("Nothing at all"),
}
// Using if let for single pattern
if let Some(x) = some_value {
println!("if let got: {}", x);
}
}

Matching Result

use std::fs::File;
use std::io::ErrorKind;
fn main() {
let result: Result<i32, &str> = Ok(42);
let error: Result<i32, &str> = Err("Something went wrong");
// Basic Result matching
match result {
Ok(val) => println!("Success: {}", val),
Err(e) => println!("Error: {}", e),
}
// More complex Result handling
let file_result = File::open("hello.txt");
match file_result {
Ok(file) => println!("File opened successfully: {:?}", file),
Err(error) => match error.kind() {
ErrorKind::NotFound => println!("File not found"),
ErrorKind::PermissionDenied => println!("Permission denied"),
other => println!("Other error: {:?}", other),
},
}
// Chaining matches for complex logic
let data = "42".parse::<i32>();
let result = match data {
Ok(num) => match num.checked_add(10) {
Some(sum) => Ok(sum),
None => Err("Overflow"),
},
Err(e) => Err(format!("Parse error: {}", e)),
};
println!("Complex result: {:?}", result);
}

3. Pattern Matching with Destructuring

Destructuring Structs

struct Point {
x: i32,
y: i32,
z: i32,
}
struct User {
name: String,
age: u32,
email: String,
}
fn main() {
let point = Point { x: 10, y: 20, z: 30 };
// Destructuring all fields
match point {
Point { x, y, z } => {
println!("Point at ({}, {}, {})", x, y, z);
}
}
// Destructuring with renaming
match point {
Point { x: a, y: b, z: c } => {
println!("Coordinates: a={}, b={}, c={}", a, b, c);
}
}
// Destructuring with ignoring
match point {
Point { x, y, .. } => {
println!("2D point: ({}, {})", x, y);
}
}
// Matching on specific values
let user = User {
name: String::from("Alice"),
age: 30,
email: String::from("[email protected]"),
};
match user {
User { name, age: 30, email } => {
println!("{} is 30 years old with email {}", name, email);
}
User { name, age, .. } => {
println!("{} is {} years old", name, age);
}
}
}

Destructuring Enums with Data

enum Shape {
Circle { radius: f64 },
Rectangle { width: f64, height: f64 },
Triangle { base: f64, height: f64 },
}
enum WebEvent {
PageLoad,
KeyPress(char),
Click { x: i64, y: i64 },
}
fn main() {
let circle = Shape::Circle { radius: 5.0 };
let rect = Shape::Rectangle { width: 10.0, height: 20.0 };
for shape in [circle, rect] {
match shape {
Shape::Circle { radius } => {
println!("Circle with radius {}", radius);
}
Shape::Rectangle { width, height } => {
println!("Rectangle {} x {}", width, height);
}
Shape::Triangle { base, height } => {
println!("Triangle base {}, height {}", base, height);
}
}
}
// Destructuring web events
let events = [
WebEvent::PageLoad,
WebEvent::KeyPress('a'),
WebEvent::Click { x: 100, y: 200 },
];
for event in events {
match event {
WebEvent::PageLoad => println!("Page loaded"),
WebEvent::KeyPress(c) => println!("Key pressed: '{}'", c),
WebEvent::Click { x, y } => println!("Clicked at ({}, {})", x, y),
}
}
}

Destructuring Tuples and Arrays

fn main() {
// Tuple destructuring
let tuple = (1, "hello", 3.14);
match tuple {
(a, b, c) => {
println!("Tuple: {} {} {}", a, b, c);
}
}
// Partial destructuring
match tuple {
(first, ..) => println!("First element: {}", first),
}
match tuple {
(.., last) => println!("Last element: {}", last),
}
// Array destructuring
let array = [1, 2, 3, 4, 5];
match array {
[first, second, ..] => {
println!("First two: {}, {}", first, second);
}
}
match array {
[.., last] => println!("Last: {}", last),
}
match array {
[first, .., last] => {
println!("First: {}, Last: {}", first, last);
}
}
// Fixed-size array destructuring
match array {
[a, b, c, d, e] => {
println!("All five: {} {} {} {} {}", a, b, c, d, e);
}
}
}

Destructuring References

fn main() {
let value = 42;
let reference = &value;
// Matching references
match reference {
&val => println!("Got value via reference: {}", val),
}
// Using ref keyword
let maybe_value: Option<String> = Some(String::from("hello"));
match maybe_value {
Some(ref s) => println!("Borrowed: {}", s), // s is &String
None => (),
}
// Still own maybe_value after match
println!("Still have: {:?}", maybe_value);
// ref mut for mutable references
let mut maybe_value = Some(String::from("world"));
match maybe_value {
Some(ref mut s) => {
s.push_str("!");
println!("Modified: {}", s);
}
None => (),
}
println!("Now have: {:?}", maybe_value);
}

4. Pattern Matching with Guards

Basic Match Guards

fn main() {
let number = 42;
match number {
x if x < 10 => println!("Small: {}", x),
x if x < 50 => println!("Medium: {}", x),
x if x < 100 => println!("Large: {}", x),
_ => println!("Huge"),
}
// Multiple conditions
match number {
x if x % 2 == 0 && x < 50 => println!("Even and less than 50"),
x if x % 2 != 0 => println!("Odd"),
_ => println!("Something else"),
}
// Guards with enums
enum Temperature {
Celsius(i32),
Fahrenheit(i32),
}
let temp = Temperature::Celsius(35);
match temp {
Temperature::Celsius(t) if t > 30 => {
println!("Hot day! {}°C", t);
}
Temperature::Celsius(t) if t < 0 => {
println!("Freezing! {}°C", t);
}
Temperature::Celsius(t) => {
println!("Pleasant {}°C", t);
}
Temperature::Fahrenheit(t) if t > 86 => {
println!("Hot day! {}°F", t);
}
Temperature::Fahrenheit(t) => {
println!("{}°F", t);
}
}
}

Guards with Bound Values

#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let point = Point { x: 5, y: 10 };
match point {
Point { x, y } if x == y => {
println!("Point on diagonal");
}
Point { x, y } if x > y => {
println!("x greater than y: {}, {}", x, y);
}
Point { x, y } => {
println!("Regular point: {}, {}", x, y);
}
}
// Guards with enum data
enum Result {
Success(i32),
Failure(String),
}
let result = Result::Success(42);
match result {
Result::Success(code) if code == 200 => {
println!("OK");
}
Result::Success(code) if code == 404 => {
println!("Not Found");
}
Result::Success(code) => {
println!("Other success code: {}", code);
}
Result::Failure(msg) if msg.contains("error") => {
println!("Error: {}", msg);
}
Result::Failure(msg) => {
println!("Other failure: {}", msg);
}
}
}

5. Range Patterns

Numeric Ranges

fn main() {
let number = 42;
match number {
0..=9 => println!("Single digit"),
10..=99 => println!("Two digits"),
100..=999 => println!("Three digits"),
_ => println!("Four or more digits"),
}
// Inclusive ranges with guard-like behavior
let score = 85;
let grade = match score {
90..=100 => 'A',
80..=89 => 'B',
70..=79 => 'C',
60..=69 => 'D',
0..=59 => 'F',
_ => '?',
};
println!("Grade: {}", grade);
// Ranges with characters
let c = 'e';
match c {
'a'..='z' => println!("Lowercase letter"),
'A'..='Z' => println!("Uppercase letter"),
'0'..='9' => println!("Digit"),
_ => println!("Other character"),
}
// Ranges with inclusive boundaries
let value = 5;
match value {
1..=5 => println!("Between 1 and 5 inclusive"),
6..=10 => println!("Between 6 and 10 inclusive"),
_ => println!("Outside range"),
}
}

Ranges with Custom Types

#[derive(PartialEq, PartialOrd, Debug)]
struct Version {
major: u32,
minor: u32,
}
impl Version {
fn new(major: u32, minor: u32) -> Self {
Version { major, minor }
}
}
// To use ranges, we need Step trait (nightly only)
// For now, we'll use guards instead
fn main() {
let version = Version::new(1, 5);
// Using guards instead of ranges for custom types
match version.major {
0 => println!("Pre-release"),
1 if version.minor <= 10 => println!("Early 1.x version"),
1 => println!("Later 1.x version"),
2..=3 => println!("Version 2 or 3"),
_ => println!("Modern version"),
}
// Simulating range with guards
match version {
Version { major: 1, minor } if minor <= 10 => {
println!("1.{} (early)", minor);
}
Version { major: 1, minor } => {
println!("1.{} (later)", minor);
}
Version { major: 2, minor } if minor < 5 => {
println!("2.{} (early 2.x)", minor);
}
Version { major, minor } => {
println!("{}.{}", major, minor);
}
}
}

6. Multiple Patterns

Matching Multiple Values

fn main() {
let number = 3;
// OR patterns with |
match number {
1 | 3 | 5 | 7 | 9 => println!("Odd digit"),
2 | 4 | 6 | 8 => println!("Even digit"),
_ => println!("Not a digit"),
}
// Combining with ranges
match number {
1 | 2 | 3..=5 => println!("Between 1 and 5 (inclusive)"),
6 | 7 | 8..=10 => println!("Between 6 and 10"),
_ => println!("Other"),
}
// Multiple patterns with enums
enum Day {
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
Sunday,
}
let day = Day::Saturday;
match day {
Day::Monday | Day::Tuesday | Day::Wednesday | Day::Thursday | Day::Friday => {
println!("Weekday");
}
Day::Saturday | Day::Sunday => {
println!("Weekend");
}
}
// OR patterns with struct destructuring
#[derive(Debug)]
enum Status {
Active,
Inactive,
Pending,
}
let status = Status::Pending;
match status {
Status::Active | Status::Pending => {
println!("Can be used");
}
Status::Inactive => {
println!("Cannot be used");
}
}
}

Nested OR Patterns

fn main() {
#[derive(Debug)]
enum Color {
RGB(u8, u8, u8),
HSV(u16, u8, u8),
Named(String),
}
let color = Color::RGB(255, 0, 0);
match color {
Color::RGB(255, 0, 0) | Color::Named(ref s) if s == "red" => {
println!("It's red!");
}
Color::RGB(0, 255, 0) | Color::Named(ref s) if s == "green" => {
println!("It's green!");
}
Color::RGB(0, 0, 255) | Color::Named(ref s) if s == "blue" => {
println!("It's blue!");
}
_ => println!("Some other color"),
}
// Complex OR with destructuring
#[derive(Debug)]
struct Container {
id: u32,
data: Option<String>,
}
let container = Container {
id: 42,
data: Some(String::from("hello")),
};
match container {
Container { id: 1 | 2 | 3, data: Some(_) } => {
println!("Special container with data");
}
Container { id: 1 | 2 | 3, data: None } => {
println!("Special container empty");
}
Container { id, data: Some(text) } if id > 100 => {
println!("Large id {} with data: {}", id, text);
}
_ => println!("Regular container"),
}
}

7. @ Bindings

Binding to Values in Patterns

fn main() {
let number = 42;
// @ syntax binds value to a variable while matching pattern
match number {
x @ 0..=9 => println!("Single digit: {}", x),
x @ 10..=99 => println!("Two digits: {}", x),
x @ 100..=999 => println!("Three digits: {}", x),
x => println!("Large number: {}", x),
}
// @ with enums
enum Message {
Hello { id: i32 },
}
let msg = Message::Hello { id: 5 };
match msg {
Message::Hello { id: id_variable @ 3..=7 } => {
println!("Found id in range: {}", id_variable);
}
Message::Hello { id: 10..=20 } => {
println!("Found id in another range (but not bound)");
}
Message::Hello { id } => {
println!("Some other id: {}", id);
}
}
// @ with Option
let optional = Some(42);
match optional {
Some(x @ 0..=50) => println!("Small number: {}", x),
Some(x @ 51..=100) => println!("Medium number: {}", x),
Some(x) => println!("Large number: {}", x),
None => println!("None"),
}
}

Complex @ Bindings

#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
fn main() {
let point = Point { x: 10, y: 20 };
// @ with struct destructuring
match point {
p @ Point { x: 10, y } => {
println!("Point with x=10 at y={}: {:?}", y, p);
}
p @ Point { x, y } if x == y => {
println!("Point on diagonal: {:?}", p);
}
p => {
println!("Regular point: {:?}", p);
}
}
// Nested @ bindings
enum Container {
Box(Point),
Bag(Vec<Point>),
}
let container = Container::Box(Point { x: 5, y: 5 });
match container {
Container::Box(p @ Point { x, y }) if x == y => {
println!("Square box at ({}, {}): {:?}", x, y, p);
}
Container::Box(p) => {
println!("Regular box: {:?}", p);
}
Container::Bag(points) if points.len() > 10 => {
println!("Large bag with {} points", points.len());
}
Container::Bag(points) => {
println!("Bag with {} points", points.len());
}
}
}

8. Match with Reference Patterns

Matching References

fn main() {
let value = 42;
let reference = &value;
// Matching a reference
match reference {
&val => println!("Got value: {}", val),
}
// Using dereference
match *reference {
val => println!("Dereferenced: {}", val),
}
// Matching mutable references
let mut value = 42;
let mut_ref = &mut value;
match mut_ref {
&mut val => {
println!("Got mutable value: {}", val);
// Can't modify here because val is a copy
}
}
// Better: use ref mut
match mut_ref {
ref mut r => {
**r += 10;
println!("Modified through ref: {}", **r);
}
}
println!("Final value: {}", value);
}

Matching on Smart Pointers

use std::rc::Rc;
use std::cell::RefCell;
fn main() {
// Rc matching
let rc = Rc::new(42);
match &*rc {
val => println!("Rc value: {}", val),
}
// RefCell matching
let cell = RefCell::new(42);
match cell.try_borrow() {
Ok(val) => println!("Borrowed value: {}", *val),
Err(_) => println!("Already borrowed"),
}
// Custom smart pointer
struct MyBox<T>(T);
impl<T> MyBox<T> {
fn new(x: T) -> MyBox<T> {
MyBox(x)
}
}
impl<T> std::ops::Deref for MyBox<T> {
type Target = T;
fn deref(&self) -> &T {
&self.0
}
}
let mybox = MyBox::new(10);
match &*mybox {
val => println!("MyBox value: {}", val),
}
}

9. Exhaustiveness and Catch-all Patterns

Ensuring Exhaustiveness

// Without catch-all, match must be exhaustive
enum Color {
Red,
Green,
Blue,
}
fn describe_color(color: Color) -> &'static str {
match color {
Color::Red => "red",
Color::Green => "green",
Color::Blue => "blue",
// No catch-all needed because all variants covered
}
}
// Adding a variant breaks exhaustiveness
enum ExtendedColor {
Red,
Green,
Blue,
Yellow, // New variant
}
fn describe_extended_color(color: ExtendedColor) -> &'static str {
match color {
ExtendedColor::Red => "red",
ExtendedColor::Green => "green",
ExtendedColor::Blue => "blue",
// Error: non-exhaustive patterns
// Need to handle Yellow or add _
ExtendedColor::Yellow => "yellow",
}
}
// Using _ for catch-all
fn handle_color(color: ExtendedColor) {
match color {
ExtendedColor::Red => println!("Red!"),
_ => println!("Not red"), // Catches Green, Blue, Yellow
}
}
// Using other catch-all patterns
fn number_category(n: i32) -> &'static str {
match n {
0 => "zero",
1 => "one",
2 => "two",
_ => "many", // Catches everything else
}
}
fn main() {
println!("{}", describe_color(Color::Red));
handle_color(ExtendedColor::Green);
handle_color(ExtendedColor::Yellow);
println!("{}", number_category(5));
}

The other and _ patterns

fn main() {
// Using _ for values we don't need
let triple = (1, 2, 3);
match triple {
(first, _, third) => {
println!("First: {}, Third: {}", first, third);
}
}
// Using .. for multiple ignored values
match triple {
(first, ..) => println!("First: {}", first),
}
match triple {
(.., last) => println!("Last: {}", last),
}
// Ignoring parts of nested structures
struct Data {
id: i32,
name: String,
metadata: (i32, i32, i32),
}
let data = Data {
id: 1,
name: String::from("test"),
metadata: (10, 20, 30),
};
match data {
Data { id, .. } => println!("Data id: {}", id),
}
}

10. Match with If Let and While Let

If Let Syntax

fn main() {
let optional = Some(42);
// Verbose match
match optional {
Some(x) => println!("Got: {}", x),
None => (),
}
// Concise if let
if let Some(x) = optional {
println!("Got: {}", x);
}
// if let with else
if let Some(x) = optional {
println!("Got: {}", x);
} else {
println!("Got nothing");
}
// if let with multiple conditions
if let Some(x) = optional {
if x > 10 {
println!("Large value: {}", x);
}
}
// if let with && (combine conditions)
if let Some(x) = optional && x > 10 {
println!("Large value: {}", x);
}
// if let with OR patterns
enum Status {
Success,
Failure,
Pending,
}
let status = Status::Success;
if let Status::Success | Status::Pending = status {
println!("Operation can proceed");
}
}

While Let Syntax

fn main() {
// while let with vector
let mut stack = vec![1, 2, 3, 4, 5];
while let Some(top) = stack.pop() {
println!("Popped: {}", top);
}
// while let with iterator
let mut iter = (0..5).into_iter();
while let Some(x) = iter.next() {
println!("Next: {}", x);
}
// while let with counter
let mut counter = Some(0);
while let Some(i) = counter {
if i > 5 {
counter = None;
} else {
println!("i = {}", i);
counter = Some(i + 1);
}
}
// while let with Result
use std::num::ParseIntError;
let strings = vec!["42", "24", "abc", "12", "xyz"];
let mut results = vec![];
for s in strings {
results.push(s.parse::<i32>());
}
// Process until first error
for result in results {
while let Ok(num) = result {
println!("Parsed: {}", num);
break; // Prevent infinite loop
}
}
// Better: process all successful results
let strings = vec!["42", "24", "abc", "12", "xyz"];
for s in strings {
if let Ok(num) = s.parse::<i32>() {
println!("Successfully parsed: {}", num);
}
}
}

11. Advanced Pattern Matching

Matching on Ranges with Custom Types

use std::ops::RangeInclusive;
#[derive(PartialEq, PartialOrd)]
struct Temperature {
celsius: f64,
}
impl Temperature {
fn new(celsius: f64) -> Self {
Temperature { celsius }
}
fn freezing() -> Self {
Temperature { celsius: 0.0 }
}
fn boiling() -> Self {
Temperature { celsius: 100.0 }
}
}
// We can't use ranges directly with custom types, but we can create helper functions
fn in_range<T: PartialOrd>(value: T, range: RangeInclusive<T>) -> bool {
range.contains(&value)
}
fn main() {
let temp = Temperature::new(25.0);
// Using guards instead of ranges
match temp.celsius {
t if t < 0.0 => println!("Below freezing"),
t if t >= 0.0 && t < 20.0 => println!("Cool"),
t if t >= 20.0 && t < 30.0 => println!("Warm"),
t if t >= 30.0 && t < 40.0 => println!("Hot"),
_ => println!("Extreme temperature"),
}
// Using helper function
let temp_range = Temperature::freezing().celsius..=Temperature::boiling().celsius;
if in_range(temp.celsius, temp_range) {
println!("Temperature is between freezing and boiling");
}
}

Matching on Strings

fn main() {
let command = "start";
// String literals
match command {
"start" => println!("Starting..."),
"stop" => println!("Stopping..."),
"pause" => println!("Pausing..."),
"resume" => println!("Resuming..."),
_ => println!("Unknown command: {}", command),
}
// With string slices
let name = "Alice";
match name {
"Alice" | "Bob" => println!("Known user"),
s if s.starts_with("admin_") => println!("Admin user: {}", s),
s => println!("Regular user: {}", s),
}
// With String
let input = String::from("quit");
match input.as_str() {
"quit" | "exit" => println!("Exiting..."),
"help" => println!("Available commands: ..."),
_ => println!("Unknown command"),
}
}

Matching on Slices

fn main() {
let numbers = vec![1, 2, 3, 4, 5];
// Match on slices
match numbers.as_slice() {
[first, second, rest @ ..] => {
println!("First: {}, Second: {}, Rest: {:?}", first, second, rest);
}
}
// Different slice patterns
let empty: Vec<i32> = vec![];
match empty.as_slice() {
[] => println!("Empty slice"),
[x] => println!("Single element: {}", x),
[x, y] => println!("Two elements: {}, {}", x, y),
[x, y, z] => println!("Three elements: {}, {}, {}", x, y, z),
_ => println!("More than three elements"),
}
// Matching with wildcards
let arr = [1, 2, 3, 4, 5];
match arr.as_slice() {
[1, .., 5] => println!("Starts with 1, ends with 5"),
[1, ..] => println!("Starts with 1"),
[.., 5] => println!("Ends with 5"),
_ => println!("Other pattern"),
}
// Matching with variable length
match arr.as_slice() {
[x @ 1..=3, ..] => println!("First element in range 1-3: {}", x),
[first, second, ..] if first == second => println!("First two equal"),
[first, .., last] if first == last => println!("First and last equal"),
_ => println!("No special pattern"),
}
}

12. Practical Examples

State Machine Implementation

#[derive(Debug, PartialEq)]
enum State {
Idle,
Connecting,
Connected,
Disconnecting,
Error(String),
}
struct Connection {
state: State,
retries: u32,
}
impl Connection {
fn new() -> Self {
Connection {
state: State::Idle,
retries: 0,
}
}
fn transition(&mut self, event: Event) {
use State::*;
match (&self.state, event) {
(Idle, Event::Connect) => {
self.state = Connecting;
self.retries = 0;
}
(Connecting, Event::Connected) => {
self.state = Connected;
println!("Connected successfully");
}
(Connecting, Event::Timeout) if self.retries < 3 => {
self.retries += 1;
println!("Retry {}/3", self.retries);
}
(Connecting, Event::Timeout) => {
self.state = Error("Max retries exceeded".to_string());
}
(Connected, Event::Disconnect) => {
self.state = Disconnecting;
}
(Connected, Event::Data(data)) => {
println!("Received data: {}", data);
}
(Disconnecting, Event::Disconnected) => {
self.state = Idle;
}
(Error(msg), _) => {
println!("In error state: {}, ignoring event", msg);
}
_ => {
println!("Invalid transition from {:?} with {:?}", self.state, event);
}
}
}
}
#[derive(Debug)]
enum Event {
Connect,
Connected,
Disconnect,
Disconnected,
Timeout,
Data(String),
}
fn main() {
let mut conn = Connection::new();
let events = vec![
Event::Connect,
Event::Timeout,
Event::Timeout,
Event::Connected,
Event::Data("Hello".to_string()),
Event::Disconnect,
Event::Disconnected,
];
for event in events {
conn.transition(event);
println!("State: {:?}", conn.state);
}
}

Expression Evaluator

#[derive(Debug, Clone)]
enum Expr {
Number(i32),
Variable(String),
BinaryOp {
left: Box<Expr>,
op: Op,
right: Box<Expr>,
},
UnaryOp {
op: UnaryOp,
expr: Box<Expr>,
},
}
#[derive(Debug, Clone)]
enum Op {
Add,
Subtract,
Multiply,
Divide,
Modulo,
}
#[derive(Debug, Clone)]
enum UnaryOp {
Negate,
Absolute,
}
struct Evaluator {
variables: std::collections::HashMap<String, i32>,
}
impl Evaluator {
fn new() -> Self {
Evaluator {
variables: std::collections::HashMap::new(),
}
}
fn evaluate(&self, expr: &Expr) -> Option<i32> {
match expr {
Expr::Number(n) => Some(*n),
Expr::Variable(name) => {
self.variables.get(name).copied()
}
Expr::BinaryOp { left, op, right } => {
let left_val = self.evaluate(left)?;
let right_val = self.evaluate(right)?;
match op {
Op::Add => Some(left_val + right_val),
Op::Subtract => Some(left_val - right_val),
Op::Multiply => Some(left_val * right_val),
Op::Divide if right_val != 0 => Some(left_val / right_val),
Op::Divide => None, // Division by zero
Op::Modulo if right_val != 0 => Some(left_val % right_val),
Op::Modulo => None, // Modulo by zero
}
}
Expr::UnaryOp { op, expr } => {
let val = self.evaluate(expr)?;
match op {
UnaryOp::Negate => Some(-val),
UnaryOp::Absolute => Some(val.abs()),
}
}
}
}
fn simplify(&self, expr: Expr) -> Expr {
match expr {
Expr::BinaryOp { left, op, right } => {
let left = self.simplify(*left);
let right = self.simplify(*right);
match (&left, &right) {
(Expr::Number(0), _) if matches!(op, Op::Add) => right,
(_, Expr::Number(0)) if matches!(op, Op::Add) => left,
(Expr::Number(1), _) if matches!(op, Op::Multiply) => right,
(_, Expr::Number(1)) if matches!(op, Op::Multiply) => left,
(Expr::Number(0), _) if matches!(op, Op::Multiply) => Expr::Number(0),
_ => Expr::BinaryOp {
left: Box::new(left),
op,
right: Box::new(right),
},
}
}
Expr::UnaryOp { op, expr } => {
let inner = self.simplify(*expr);
match (op, &inner) {
(UnaryOp::Negate, Expr::Number(n)) => Expr::Number(-n),
(UnaryOp::Absolute, Expr::Number(n)) => Expr::Number(n.abs()),
_ => Expr::UnaryOp {
op,
expr: Box::new(inner),
},
}
}
other => other,
}
}
}
fn main() {
let evaluator = Evaluator::new();
// Build expression: (5 + 3) * 2
let expr = Expr::BinaryOp {
left: Box::new(Expr::BinaryOp {
left: Box::new(Expr::Number(5)),
op: Op::Add,
right: Box::new(Expr::Number(3)),
}),
op: Op::Multiply,
right: Box::new(Expr::Number(2)),
};
println!("Original: {:?}", expr);
println!("Result: {:?}", evaluator.evaluate(&expr));
// Test simplification
let expr = Expr::BinaryOp {
left: Box::new(Expr::Number(0)),
op: Op::Add,
right: Box::new(Expr::Number(42)),
};
let simplified = evaluator.simplify(expr);
println!("Simplified: {:?}", simplified);
}

Configuration Parser

#[derive(Debug)]
struct Config {
host: String,
port: u16,
timeout: u64,
retries: u32,
features: Vec<String>,
}
impl Config {
fn parse_line(&mut self, line: &str) -> Result<(), String> {
let line = line.trim();
// Skip empty lines and comments
match line {
"" | _ if line.starts_with('#') => return Ok(()),
_ => (),
}
// Split into key and value
let parts: Vec<&str> = line.splitn(2, '=').collect();
match parts.as_slice() {
[key, value] => {
let key = key.trim();
let value = value.trim();
match (key, value) {
("host", host) => {
self.host = host.to_string();
Ok(())
}
("port", port) => {
match port.parse::<u16>() {
Ok(p) => {
self.port = p;
Ok(())
}
Err(_) => Err(format!("Invalid port: {}", port)),
}
}
("timeout", timeout) => {
match timeout.parse::<u64>() {
Ok(t) => {
self.timeout = t;
Ok(())
}
Err(_) => Err(format!("Invalid timeout: {}", timeout)),
}
}
("retries", retries) => {
match retries.parse::<u32>() {
Ok(r) => {
self.retries = r;
Ok(())
}
Err(_) => Err(format!("Invalid retries: {}", retries)),
}
}
("features", features) => {
self.features = features
.split(',')
.map(|s| s.trim().to_string())
.collect();
Ok(())
}
_ => Err(format!("Unknown key: {}", key)),
}
}
[single] if single.contains('=') => {
Err(format!("Empty value for key: {}", single))
}
_ => Err(format!("Invalid line format: {}", line)),
}
}
fn parse(&mut self, input: &str) -> Result<(), Vec<String>> {
let mut errors = Vec::new();
for (line_num, line) in input.lines().enumerate() {
if let Err(e) = self.parse_line(line) {
errors.push(format!("Line {}: {}", line_num + 1, e));
}
}
if errors.is_empty() {
Ok(())
} else {
Err(errors)
}
}
}
fn main() {
let config_input = r#"
# Server configuration
host = localhost
port = 8080
timeout = 30
retries = 3
features = logging,metrics,debug
# This line has an error
invalid_line
port = invalid
"#;
let mut config = Config {
host: String::new(),
port: 0,
timeout: 0,
retries: 0,
features: vec![],
};
match config.parse(config_input) {
Ok(()) => println!("Parsed config: {:?}", config),
Err(errors) => {
println!("Errors parsing config:");
for error in errors {
println!("  {}", error);
}
}
}
}

Conclusion

The match expression is one of Rust's most powerful and expressive features:

Key Takeaways

  1. Exhaustiveness: The compiler ensures all cases are handled
  2. Pattern Matching: Can match on values, structures, ranges, and more
  3. Destructuring: Break apart complex data types directly in patterns
  4. Guards: Add conditional logic to patterns
  5. Bindings: Capture parts of patterns with @
  6. Multiple Patterns: Use | for OR patterns
  7. if let/while let: Concise syntax for single patterns

Common Patterns

  • Enum matching: Handle different variants with their associated data
  • Option/Result handling: Safely handle optional values and errors
  • State machines: Model state transitions cleanly
  • Parsing: Destructure and validate input
  • Configuration: Parse and validate configuration files

Best Practices

  1. Always be exhaustive: Use _ for catch-all when needed
  2. Prefer specific patterns over catch-all when possible
  3. Use guards for complex conditions
  4. Use if let for single-pattern matches
  5. Keep match arms simple - extract complex logic to functions
  6. Use @ bindings when you need both the whole value and parts
  7. Consider performance - match compiles to efficient branch tables

The match expression embodies Rust's philosophy of making impossible states impossible and forcing you to handle all cases, leading to more robust and maintainable code.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper