What Are Enums in Rust?
Enums, short for “enumerations,” are custom types that can represent multiple related values, each as a variant. They are particularly useful for scenarios where a value can only be one of a predefined set of possibilities.
Defining an Enum
You define an enum using the enum keyword followed by the name and its variants.
Syntax:
enum EnumName {
Variant1,
Variant2,
Variant3,
}
Example:
enum Direction {
North,
East,
South,
West,
}
fn main() {
let direction = Direction::North;
match direction {
Direction::North => println!("Heading North"),
Direction::East => println!("Heading East"),
Direction::South => println!("Heading South"),
Direction::West => println!("Heading West"),
}
}
Output:
Heading North
Features of Rust Enums
- Multiple Variants: Each enum can have several variants.
- Associated Data: Variants can carry additional data, making enums versatile.
- Pattern Matching: Enums work seamlessly with Rust’s powerful pattern matching.
- Type Safety: Enums ensure that only valid values are used.
Enums with Associated Data
Variants in an enum can include additional data, allowing you to store extra information.
Example:
enum Message {
Text(String),
Image { url: String, size: u32 },
Quit,
}
fn main() {
let msg = Message::Image {
url: String::from("https://example.com/image.png"),
size: 1024,
};
match msg {
Message::Text(content) => println!("Text message: {}", content),
Message::Image { url, size } => println!("Image URL: {}, Size: {}", url, size),
Message::Quit => println!("Quit message received."),
}
}
Output:
Image URL: https://example.com/image.png, Size: 1024
Enums with Methods
You can define methods for enums using the impl block. This makes enums more versatile and encapsulated.
Example:
enum TrafficLight {
Red,
Yellow,
Green,
}
impl TrafficLight {
fn time(&self) -> u32 {
match self {
TrafficLight::Red => 30,
TrafficLight::Yellow => 5,
TrafficLight::Green => 60,
}
}
}
fn main() {
let light = TrafficLight::Green;
println!("Green light duration: {} seconds", light.time());
}
Output:
Green light duration: 60 seconds
Using Enums with Pattern Matching
Pattern matching is a core feature of Rust that works seamlessly with enums. It allows you to handle different enum variants efficiently.
Example:
enum Result {
Success(String),
Error(i32),
}
fn main() {
let operation = Result::Success(String::from("File saved successfully"));
match operation {
Result::Success(msg) => println!("Operation succeeded: {}", msg),
Result::Error(code) => println!("Operation failed with error code: {}", code),
}
}
Output:
Operation succeeded: File saved successfully
Enums and Option Type
The Option
enum is a built-in example in Rust and is widely used to represent optional values.
Definition:
enum Option<T> {
Some(T),
None,
}
Example:
fn divide(a: i32, b: i32) -> Option<f64> {
if b == 0 {
None
} else {
Some(a as f64 / b as f64)
}
}
fn main() {
match divide(10, 2) {
Some(result) => println!("Result: {}", result),
None => println!("Cannot divide by zero"),
}
}
Output:
Result: 5
Common Use Cases of Enums
- Representing States: Enums are great for representing states in a program, such as Loading, Error, or Success.
- Handling Variants: Use enums to handle multiple related types in a structured way.
- Error Handling: Enums like Result make error handling safe and robust.
Advanced Enums: Recursive Enums
Enums can refer to themselves, making them suitable for complex structures like linked lists or trees.
Example:
enum List {
Node(i32, Box<List>),
Nil,
}
fn main() {
let list = List::Node(1, Box::new(List::Node(2, Box::new(List::Nil))));
// Represents: 1 -> 2 -> Nil
}
Enums vs Structs
Feature | Enum | Struct |
---|---|---|
Variants | Multiple variants in a single type | Fixed fields |
Data Flexibility | Each variant can have different data types | Fields have defined types |
Use Case | Represent choices or states | Group related data into one entity |