10.4 Using Enums in Code: Pattern Matching
Since enum instances can represent different variants with potentially different data, you need a way to determine which variant you have and act accordingly. Rust’s primary tool for this is pattern matching using the match
keyword.
10.4.1 The match
Expression
A match
expression compares a value against a series of patterns. When a pattern matches, the associated code block (the “arm”) executes. match
in Rust is exhaustive: the compiler ensures all possible variants are handled.
#[derive(Debug)] enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(u8, u8, u8), } fn process_message(msg: Message) { // 'match' is an expression; its result can be used match msg { // Pattern for the Quit variant (no data to bind) Message::Quit => { println!("Quit message received."); } // Pattern matching specific values within a variant Message::Move { x: 0, y: 0 } => { println!("Move message: At the origin."); } // Pattern binding data fields to variables x and y Message::Move { x, y } => { println!("Move message: To coordinates x: {}, y: {}", x, y); } // Pattern binding tuple variant data to 'text' Message::Write(text) => { println!("Write message: '{}'", text); // 'text' is bound to the String inside Message::Write } // Pattern binding tuple variant data to r, g, b Message::ChangeColor(r, g, b) => { println!("ChangeColor message: R={}, G={}, B={}", r, g, b); } // No 'default' or '_' needed here because all Message // variants are explicitly handled. The compiler checks this! } } fn main() { let messages = vec![ Message::Quit, Message::Move { x: 0, y: 0 }, // Will match the specific pattern first Message::Move { x: 15, y: 25 }, // Will match the general {x, y} pattern Message::Write(String::from("Pattern Matching Rocks!")), Message::ChangeColor(100, 200, 50), ]; for msg in messages { // Note: 'messages' vector owns the String in Write. // 'process_message' takes ownership of 'msg'. println!("Processing: {:?}", msg); // Debug print before moving process_message(msg); println!("---"); } }
- Patterns & Arms: Each
VARIANT => { code }
is a match arm. The part before=>
is the pattern. - Destructuring: Patterns can extract data from variants.
Message::Move { x, y }
binds the fieldsx
andy
to local variablesx
andy
.Message::Write(text)
binds the innerString
to the local variabletext
.Message::Move { x: 0, y: 0 }
matches only ifx
is0
andy
is0
.
- Order Matters: Arms are checked top-down. The first matching arm executes. Place specific patterns before more general ones.
- Exhaustiveness: Forgetting a variant causes a compile-time error. Use the wildcard
_
to handle remaining variants collectively if needed:
// Hidden setup code for the wildcard example #[derive(Debug)] enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(u8, u8, u8), } fn process_message_partial(msg: Message) { match msg { Message::Quit => println!("Quitting."), Message::Write(text) => println!("Writing: {}", text.chars().count()), // The wildcard '_' matches any value not handled above _ => println!("Some other message type received."), } } fn main() { process_message_partial(Message::Quit); process_message_partial(Message::Move{ x: 1, y: 1}); process_message_partial(Message::Write(String::from("Hi"))); }
match
is an Expression: Amatch
evaluates to a value. All arms must return values of the same type.
Advanced pattern matching (guards, @
bindings) will be covered in Chapter 21.
10.4.2 Concise Control Flow with if let
When you only care about one specific variant, if let
is typically more concise than a match
expression that requires handling all other variants, often using a _ => {}
catch-all arm.
Using match
(for one variant):
// Hidden setup code #[derive(Debug)] enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(u8, u8, u8), } fn main() { let msg = Message::Write(String::from("Handle only this")); match msg { Message::Write(text) => { println!("Handling Write message: {}", text); } _ => {} // Ignore all other variants silently } }
Using if let
:
#[derive(Debug)] enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(u8, u8, u8), } fn main() { let msg = Message::Write(String::from("Handle only this")); // Check if 'msg' matches the 'Message::Write' pattern if let Message::Write(text) = msg { // If it matches, 'text' is bound, and this block executes println!("Handling Write message via if let: {}", text); // Note: 'msg' is partially moved here if 'text' is not borrowed. } else { // Optional 'else' block executes if the pattern doesn't match println!("Not a Write message."); } let msg2 = Message::Quit; if let Message::Write(text) = msg2 { println!("This won't execute for msg2: {}", text); } else { println!("msg2 was not a Write message."); // This will execute } }
- Syntax:
if let PATTERN = EXPRESSION { /* if matches */ } else { /* if not */ }
- Functionality: Tests if
EXPRESSION
matchesPATTERN
. Binds variables on match. Executes theif
block on match,else
block otherwise. - Use Case: Convenient for handling one specific variant, optionally with an
else
for all others. Less boilerplate thanmatch
.
Chain else if let
to handle a few specific cases sequentially:
#[derive(Debug)] enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(u8, u8, u8), } fn check_specific_messages(msg: Message) { if let Message::Quit = msg { println!("It's a Quit message."); } else if let Message::Move { x, y } = msg { println!("It's a Move message to ({}, {}).", x, y); } else { // Final else handles anything not matched above println!("It's some other message ({:?}).", msg); } } fn main() { check_specific_messages(Message::Move { x: 5, y: -5 }); check_specific_messages(Message::Write(String::from("Hello"))); check_specific_messages(Message::Quit); }
For handling more than two or three variants or complex logic, a full match
is usually clearer and leverages exhaustiveness checking better.
10.4.3 Defining Methods on Enums
Associate methods with an enum using an impl
block, just like with structs, to encapsulate behavior.
#[derive(Debug)] enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(u8, u8, u8), } // Implementation block for the Message enum impl Message { // Method taking an immutable reference to self fn describe(&self) -> String { // Use 'match' inside the method on 'self' match self { Message::Quit => "A Quit message".to_string(), Message::Move { x, y } => format!("A Move message to ({}, {})", x, y), Message::Write(text) => format!("A Write message: '{}'", text), Message::ChangeColor(r, g, b) => format!("A ChangeColor message ({},{},{})", r, g, b), } } // Another method fn is_quit(&self) -> bool { // Match can directly return a boolean match self { Message::Quit => true, _ => false, // All other variants are not Quit } } } fn main() { let messages = vec![ Message::Move { x: 1, y: 1 }, Message::Quit, Message::Write(String::from("Method call example")), ]; for msg in &messages { // Iterate over references (&Message) println!("Description: {}", msg.describe()); // Call method if msg.is_quit() { println!(" (Detected Quit message via method)"); } } }
- Encapsulation: Methods group behavior with the enum definition.
self
: Refers to the enum instance. Pattern matching onself
is common within methods.