Beginner Level
Enums and Pattern Matching
Chapter 7: Enums and Pattern Matching šÆ
Enums allow you to define types by enumerating their possible variants. They're incredibly powerful in Rust!
Defining Enums
Basic Enum
enum IpAddrKind {
V4,
V6,
}
fn main() {
let four = IpAddrKind::V4;
let six = IpAddrKind::V6;
route(IpAddrKind::V4);
route(IpAddrKind::V6);
}
fn route(ip_kind: IpAddrKind) {
// routing logic here
}Enums with Data
enum IpAddr {
V4(u8, u8, u8, u8),
V6(String),
}
fn main() {
let home = IpAddr::V4(127, 0, 0, 1);
let loopback = IpAddr::V6(String::from("::1"));
}Complex Enum Variants
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
ChangeColor(i32, i32, i32),
}
impl Message {
fn call(&self) {
match self {
Message::Quit => println!("Quit"),
Message::Move { x, y } => println!("Move to ({}, {})", x, y),
Message::Write(text) => println!("Write: {}", text),
Message::ChangeColor(r, g, b) => println!("Change color to ({}, {}, {})", r, g, b),
}
}
}
fn main() {
let m = Message::Write(String::from("hello"));
m.call();
}The Option Enum
Handling Null Values Safely
fn main() {
let some_number = Some(5);
let some_string = Some("a string");
let absent_number: Option<i32> = None;
println!("{:?}", some_number);
println!("{:?}", some_string);
println!("{:?}", absent_number);
}Working with Option
fn plus_one(x: Option<i32>) -> Option<i32> {
match x {
None => None,
Some(i) => Some(i + 1),
}
}
fn main() {
let five = Some(5);
let six = plus_one(five);
let none = plus_one(None);
println!("{:?}", six);
println!("{:?}", none);
}Pattern Matching with match
Exhaustive Matching
#[derive(Debug)]
enum UsState {
Alabama,
Alaska,
California,
// ... etc
}
enum Coin {
Penny,
Nickel,
Dime,
Quarter(UsState),
}
fn value_in_cents(coin: Coin) -> u8 {
match coin {
Coin::Penny => {
println!("Lucky penny!");
1
}
Coin::Nickel => 5,
Coin::Dime => 10,
Coin::Quarter(state) => {
println!("State quarter from {:?}!", state);
25
}
}
}
fn main() {
let coin = Coin::Quarter(UsState::Alaska);
println!("Value: {} cents", value_in_cents(coin));
}Catch-all Patterns
fn main() {
let dice_roll = 9;
match dice_roll {
3 => add_fancy_hat(),
7 => remove_fancy_hat(),
other => move_player(other),
}
// Or use _ to ignore the value
match dice_roll {
3 => add_fancy_hat(),
7 => remove_fancy_hat(),
_ => reroll(),
}
}
fn add_fancy_hat() {}
fn remove_fancy_hat() {}
fn move_player(num_spaces: u8) {}
fn reroll() {}if let Syntax
Concise Control Flow
fn main() {
let config_max = Some(3u8);
// Instead of this verbose match:
match config_max {
Some(max) => println!("The maximum is configured to be {}", max),
_ => (),
}
// Use if let:
if let Some(max) = config_max {
println!("The maximum is configured to be {}", max);
}
}if let with else
fn main() {
let mut count = 0;
let coin = Coin::Quarter(UsState::Alaska);
if let Coin::Quarter(state) = coin {
println!("State quarter from {:?}!", state);
} else {
count += 1;
}
}Result Enum
Error Handling
use std::fs::File;
use std::io::ErrorKind;
fn main() {
let greeting_file_result = File::open("hello.txt");
let greeting_file = match greeting_file_result {
Ok(file) => file,
Err(error) => match error.kind() {
ErrorKind::NotFound => match File::create("hello.txt") {
Ok(fc) => fc,
Err(e) => panic!("Problem creating the file: {:?}", e),
},
other_error => {
panic!("Problem opening the file: {:?}", other_error);
}
},
};
}Advanced Pattern Matching
Guards
fn main() {
let num = Some(4);
match num {
Some(x) if x % 2 == 0 => println!("The number {} is even", x),
Some(x) => println!("The number {} is odd", x),
None => (),
}
}@ Bindings
enum Message {
Hello { id: i32 },
}
fn main() {
let msg = Message::Hello { id: 5 };
match msg {
Message::Hello {
id: id_variable @ 3..=7,
} => println!("Found an id in range: {}", id_variable),
Message::Hello { id: 10..=12 } => {
println!("Found an id in another range")
}
Message::Hello { id } => println!("Found some other id: {}", id),
}
}Practical Examples
Example 1: HTTP Status Codes
#[derive(Debug)]
enum HttpStatus {
Ok,
NotFound,
InternalServerError,
Custom(u16),
}
impl HttpStatus {
fn code(&self) -> u16 {
match self {
HttpStatus::Ok => 200,
HttpStatus::NotFound => 404,
HttpStatus::InternalServerError => 500,
HttpStatus::Custom(code) => *code,
}
}
fn message(&self) -> &str {
match self {
HttpStatus::Ok => "OK",
HttpStatus::NotFound => "Not Found",
HttpStatus::InternalServerError => "Internal Server Error",
HttpStatus::Custom(_) => "Custom Status",
}
}
}
fn main() {
let statuses = vec![
HttpStatus::Ok,
HttpStatus::NotFound,
HttpStatus::Custom(418),
];
for status in statuses {
println!("{}: {} - {}", status.code(), status.message(), format!("{:?}", status));
}
}Example 2: Calculator with Error Handling
#[derive(Debug)]
enum MathError {
DivisionByZero,
NegativeLogarithm,
NegativeSquareRoot,
}
type MathResult = Result<f64, MathError>;
fn divide(x: f64, y: f64) -> MathResult {
if y == 0.0 {
Err(MathError::DivisionByZero)
} else {
Ok(x / y)
}
}
fn sqrt(x: f64) -> MathResult {
if x < 0.0 {
Err(MathError::NegativeSquareRoot)
} else {
Ok(x.sqrt())
}
}
fn main() {
let operations = vec![
divide(10.0, 2.0),
divide(10.0, 0.0),
sqrt(16.0),
sqrt(-4.0),
];
for op in operations {
match op {
Ok(result) => println!("Result: {}", result),
Err(e) => println!("Error: {:?}", e),
}
}
}Key Takeaways
- ā Enums define types by enumerating possible variants
- ā Variants can hold data of different types
- ā
matchexpressions must be exhaustive - ā
Option<T>replaces null values safely - ā
Result<T, E>handles errors explicitly - ā
if letprovides concise pattern matching
Ready for Chapter 8? ā Collections