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:
Quithas no data.Movehas named fieldsxandy.Writehas a singleStringfield.ChangeColorhas threei32fields.
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
Statuswith variantsSuccess,Error(String), andLoading. - Write a function
print_statusthat takes aStatusand 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
Shapewith variantsCircle(f64),Rectangle { width: f64, height: f64 }, andTriangle(f64, f64, f64). - Write a function
describe_shapethat takes aShapeand 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
matchstatement. 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.
