In this section, we will explore Rust's powerful enum
type and the concept of pattern matching. Enums allow you to define a type by enumerating its possible variants, and pattern matching provides a concise and readable way to handle different cases of an enum.
What are Enums?
Enums, short for "enumerations," are a way to define a type that can be one of several variants. Each variant can optionally have associated data. Enums are particularly useful for representing a value that can be one of a few different states.
Defining Enums
To define an enum in Rust, use the enum
keyword followed by the name of the enum and its variants:
In this example, Direction
is an enum with four variants: North
, South
, East
, and West
.
Enums with Associated Data
Enums can also have variants that contain data. This is useful for representing more complex states:
Here, the Message
enum has four variants:
Quit
has no data.Move
has named fieldsx
andy
.Write
has a singleString
field.ChangeColor
has threei32
fields.
Pattern Matching
Pattern matching in Rust is a powerful feature that allows you to match a value against a pattern and execute code based on which pattern matches. The match
keyword is used for pattern matching.
Basic Pattern Matching
Here's an example of pattern matching with the Direction
enum:
fn direction_message(direction: Direction) { match direction { Direction::North => println!("Heading North!"), Direction::South => println!("Heading South!"), Direction::East => println!("Heading East!"), Direction::West => println!("Heading West!"), } } fn main() { let dir = Direction::North; direction_message(dir); }
In this example, the direction_message
function takes a Direction
and prints a message based on the variant.
Pattern Matching with Associated Data
When matching enums with associated data, you can extract the data as part of the pattern:
fn process_message(msg: Message) { match msg { Message::Quit => println!("Quit message received."), Message::Move { x, y } => println!("Move to coordinates: ({}, {})", x, y), Message::Write(text) => println!("Message: {}", text), Message::ChangeColor(r, g, b) => println!("Change color to RGB: ({}, {}, {})", r, g, b), } } fn main() { let msg = Message::Move { x: 10, y: 20 }; process_message(msg); }
In this example, the process_message
function matches on the Message
enum and extracts the associated data for each variant.
Practical Exercises
Exercise 1: Define and Match Enums
- Define an enum
Status
with variantsSuccess
,Error(String)
, andLoading
. - Write a function
print_status
that takes aStatus
and prints a message based on the variant.
Solution
enum Status { Success, Error(String), Loading, } fn print_status(status: Status) { match status { Status::Success => println!("Operation was successful!"), Status::Error(msg) => println!("Error: {}", msg), Status::Loading => println!("Loading..."), } } fn main() { let status = Status::Error(String::from("File not found")); print_status(status); }
Exercise 2: Complex Enums and Pattern Matching
- Define an enum
Shape
with variantsCircle(f64)
,Rectangle { width: f64, height: f64 }
, andTriangle(f64, f64, f64)
. - Write a function
describe_shape
that takes aShape
and prints a description of the shape.
Solution
enum Shape { Circle(f64), Rectangle { width: f64, height: f64 }, Triangle(f64, f64, f64), } fn describe_shape(shape: Shape) { match shape { Shape::Circle(radius) => println!("Circle with radius: {}", radius), Shape::Rectangle { width, height } => println!("Rectangle with width: {} and height: {}", width, height), Shape::Triangle(a, b, c) => println!("Triangle with sides: {}, {}, {}", a, b, c), } } fn main() { let shape = Shape::Rectangle { width: 10.0, height: 20.0 }; describe_shape(shape); }
Common Mistakes and Tips
- Exhaustive Matching: Ensure that all possible variants of an enum are covered in a
match
statement. Rust will give a compile-time error if any variant is not handled. - Use
_
for Catch-All: If you don't need to handle all variants explicitly, you can use_
as a catch-all pattern. - Destructuring: Take advantage of pattern matching to destructure and extract data from enum variants.
Conclusion
In this section, we covered the basics of enums and pattern matching in Rust. Enums allow you to define types with multiple variants, and pattern matching provides a powerful way to handle these variants. By practicing with enums and pattern matching, you can write more expressive and robust Rust code.
Next, we will explore collections in Rust, starting with vectors.