Sajiron
Related Post
📖 If you haven't read it yet, check out the previous blog: Deep Dive into Rust Structs: A Comprehensive Guide
Enums in Rust are a powerful feature that allows defining a type with multiple possible variants. They provide a clean way to represent different states or conditions in a program, making control flow more structured and expressive.
In this blog, we will explore:
✅ Defining and using enums
✅ Assigning values to enums
✅ Enums with associated data
✅ Pattern matching with enums
✅ Advanced enum techniques
By the end of this guide, you'll have a strong understanding of how to leverage enums effectively in Rust. Let’s dive in! 🚀
Enums (short for "enumerations") allow you to define a type with multiple possible values. Unlike structs, where an instance contains all fields, an enum instance can hold only one of its defined variants at a time. This is useful for scenarios where a value should be one of a fixed set of options.
For example, if we want to represent movement directions (Up
, Down
, Left
, Right
), enums are an ideal choice.
Example: Basic Enum
enum Direction {
Up,
Down,
Left,
Right,
}
fn move_character(direction: Direction) {
match direction {
Direction::Up => println!("Moving up"),
Direction::Down => println!("Moving down"),
Direction::Left => println!("Moving left"),
Direction::Right => println!("Moving right"),
}
}
fn main() {
let dir = Direction::Up;
move_character(dir);
}
Enums can also be used to store integer or string values directly in their variants, making them more expressive.
Example: Enum with Integer Values
enum StatusCode {
Success = 200,
NotFound = 404,
InternalError = 500,
}
fn main() {
let code = StatusCode::Success as i32;
println!("Success Code: {}", code);
}
Rust does not allow assigning string values directly to enum variants like integers. However, we can achieve this using tuple variants or implementing a method to return a string.
💡 Side Note: String
is heap-allocated and mutable, making it useful for dynamic text, while &'static str
is an immutable reference to a string stored in the program’s binary. Use String
when you need flexibility and &'static str
when you want efficiency for constant strings.
Example: Using Tuple Variants
enum StatusMessage {
Success(&'static str),
Error(String),
}
fn main() {
let success = StatusMessage::Success("OK");
let error = StatusMessage::Error(String::from("Network failure"));
match success {
StatusMessage::Success(msg) => println!("Success: {}", msg),
StatusMessage::Error(msg) => println!("Error: {}", msg),
}
}
Example: Using impl
to Return a String
enum Status {
Success,
NotFound,
InternalError,
}
impl Status {
fn message(&self) -> &'static str {
match self {
Status::Success => "OK",
Status::NotFound => "Not Found",
Status::InternalError => "Internal Server Error",
}
}
}
fn main() {
let status = Status::Success;
println!("Status: {}", status.message());
}
Unlike basic enums, variants can store additional data, making them more powerful and flexible. This allows us to associate specific values with each variant, making enums more versatile.
Example: Enum with Data
enum Message {
Quit,
Move { x: i32, y: i32 },
Write(String),
}
fn process_message(msg: Message) {
match msg {
Message::Quit => println!("Exiting..."),
Message::Move { x, y } => println!("Moving to ({}, {})", x, y),
Message::Write(text) => println!("Message: {}", text),
}
}
fn main() {
let msg = Message::Write(String::from("Hello, Rust!"));
process_message(msg);
}
Option<T>
and Result<T, E>
Option<T>
Rust avoids null values, which can lead to runtime errors in many other languages. Instead, it provides Option<T>
for handling cases where a value may or may not exist. This ensures that missing values are explicitly managed at compile time, reducing unexpected crashes.
Example: Using Option<T>
fn divide(numerator: f64, denominator: f64) -> Option<f64> {
if denominator == 0.0 {
None
} else {
Some(numerator / denominator)
}
}
fn main() {
match divide(10.0, 2.0) {
Some(result) => println!("Result: {}", result),
None => println!("Cannot divide by zero!"),
}
}
Result<T, E>
?Rust provides the Result<T, E>
enum for error handling, where T
represents the successful return type and E
represents the error type. This forces developers to handle errors explicitly, preventing unexpected crashes.
Result<T, E>
fn divide(numerator: f64, denominator: f64) -> Result<f64, String> {
if denominator == 0.0 {
Err(String::from("Cannot divide by zero!"))
} else {
Ok(numerator / denominator)
}
}
fn main() {
match divide(10.0, 2.0) {
Ok(result) => println!("Result: {}", result),
Err(error) => println!("Error: {}", error),
}
}
Enums can also be used with if
statements, particularly when checking for a specific variant. Since enums represent multiple variants, you typically use if let
for pattern matching a single case.
Example: Using if let
A more structured way to use if let
is by retrieving an enum value from a function and checking its variant before displaying a message.
enum Status {
Success,
Failure(String),
}
fn get_status(status_code: i32) -> Status {
if status_code == 400 {
Status::Failure(String::from("Bad data"))
} else {
Status::Failure(String::from("Network error"))
}
}
fn main() {
let status = get_status(400);
if let Status::Failure(reason) = &status {
println!("Error: {}", reason);
} else {
println!("Operation succeeded!");
}
}
Alternatively, you can compare enums directly if they don’t store additional data:
#[derive(PartialEq)] // Enables direct comparison
enum Status {
Success,
Failure(String),
}
fn main() {
let status = Status::Success;
if status == Status::Success {
println!("Operation succeeded!");
}
}
By now, you should have a solid understanding of Rust enums and how they simplify handling multiple states in a structured way.
Stay tuned for the next blog on Traits in Rust! 🚀
💡 If you found this helpful, please remember to leave a like! 👍
Learn how to use Rust's core collections—Vec, HashMap, and String—with real-world examples, common methods, and usage patterns.
Learn how to create, read, write, and manage files and directories in Rust with practical examples and error handling best practices.
Build fast and safe frontend apps using Rust and Yew. Learn setup, components, hooks, and WebAssembly—all in a React-like Rust framework.
Learn how to structure Rust projects with packages, crates, and modules for better maintainability, reusability, and scalability.