10.4 Using Enums in Code
Because enum variants can store different types of data, you must handle them carefully.
10.4.1 Pattern Matching with Enums
Rust’s pattern matching lets you compare a value against one or more patterns, binding variables to matched data. Once a pattern matches, the corresponding block runs:
enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(i32, i32, i32), } fn process_message(msg: Message) { match msg { Message::Quit => println!("Quit message"), Message::Move { x: 0, y: 0 } => println!("Not moving at all"), Message::Move { x, y } => println!("Move to x: {}, y: {}", x, y), Message::Write(text) => println!("Write message: {}", text), Message::ChangeColor(r, g, b) => { println!("Change color to red: {}, green: {}, blue: {}", r, g, b) } } } fn main() { let msg = Message::Move { x: 0, y: 0 }; process_message(msg); }
- Destructuring: Match arms can specify inner values, such as
x: 0
. - Order: The first matching pattern applies.
- Completeness: Every variant must be handled or covered by a wildcard
_
.
We’ll explore advanced pattern matching techniques in Chapter 21.
10.4.2 The ‘if let’ Syntax
When you’re only interested in a single variant (and what to do if it matches), if let
can be more concise than a full match
.
Using match
:
enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(i32, i32, i32), } fn main() { let msg = Message::Write(String::from("Hello")); match msg { Message::Write(text) => println!("Message is: {}", text), _ => println!("Message is not a Write variant"), } }
Here, we don’t care about any variant other than Message::Write
. The _
pattern covers everything else.
Using if let
:
enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(i32, i32, i32), } fn main() { let msg = Message::Write(String::from("Hello")); if let Message::Write(text) = msg { println!("Message is: {}", text); } else { println!("Message is not a Write variant"); } }
if let Message::Write(text) = msg
: Checks ifmsg
is theWrite
variant. If so,text
is bound to the containedString
.else
: Handles any variant that isn’tMessage::Write
.
You can chain multiple if let
expressions with else if let
:
enum Message { Quit, Move { x: i32, y: i32 }, Write(String), ChangeColor(i32, i32, i32), } fn main() { let msg = Message::Move { x: 0, y: 0 }; if let Message::Write(text) = msg { println!("Message is: {}", text); } else if let Message::Move { x: 0, y: 0 } = msg { println!("Not moving at all"); } else { println!("Message is something else"); } }
else if let
: Lets you check additional patterns in sequence. Each block only runs if its pattern matches and all previous conditions were not met.
In practice, when multiple variants must be handled, a full match
is usually clearer and ensures you account for every possibility. However, for a single variant that needs special treatment, if let
makes the code more concise and readable.
10.4.3 Methods on Enums
Enums can define methods in an impl
block, just like structs:
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"), Message::Move { x: 0, y: 0 } => println!("Not moving at all"), Message::Move { x, y } => println!("Move to x: {}, y: {}", x, y), Message::Write(text) => println!("Write message: {}", text), Message::ChangeColor(r, g, b) => { println!("Change color to red: {}, green: {}, blue: {}", r, g, b) } } } } fn main() { let msg = Message::Move { x: 0, y: 0 }; msg.call(); }
- Encapsulation: Behavior is directly associated with the enum.
- Internal Pattern Matching: Each variant is handled within the
call
method.