Introduction to Rust Enums
Enums in Rust are unique among programming languages because they allow each variant to hold different types of data.
Defining, Assigning, and Printing Enums
Rust enums have several distinctive characteristics:
- Enum variants can contain data or be simple
- When variants contain data, different instances can hold different types
- Enum variant names must folow specific naming conventions
- Enums can have methods, similar to structs
Enum Examples
#[derive(Debug)]
enum Gender {
Male,
Female,
NonBinary
}
// Enum with associated data
#[derive(Debug)]
enum Hue {
Red,
Blue = 5
}
// Enum with multiple variants containing data of the same type
#[derive(Debug, PartialEq)]
enum Performance {
Excellent(u32, String),
Good(u32, String),
NeedsImprovement(u32, String)
}
// Implementation of methods for enum
impl Performance {
fn display(&self) {
match self {
Performance::Excellent(score, comment) => println!("Rating: {} - {}", score, comment),
Performance::Good(score, comment) => println!("Rating: {} - {}", score, comment),
Performance::NeedsImprovement(score, comment) => println!("Rating: {} - {}", score, comment)
}
}
}
#[derive(Debug)]
enum Food<'a>{
Vegetables([String;5]),
Poultry,
Pork,
BeefLamb,
Seafood,
Grains,
Fruits(&'a str, &'a str, &'a str)
}
fn main() {
let gender = Gender::Male;
println!("{:?}", gender);
let rating = Hue::Blue;
println!("{:?}", rating);
let red = Hue::Red;
println!("{:?}", red);
let excellent = Performance::Excellent(95, String::from("Outstanding"));
let good = Performance::Good(80, String::from("Satisfactory"));
excellent.display();
println!("{:?}", excellent);
if excellent == good {
println!("They're equal");
} else {
println!("They're not equal");
}
let fruit_basket = Food::Fruits("Apple", "Banana", "Orange");
println!("{:?}", fruit_basket);
display_fruit(fruit_basket);
let veggies = Food::Vegetables(["Broccoli".to_string(), "Carrot".to_string(), "Spinach".to_string(), "Kale".to_string(), "Lettuce".to_string()]);
println!("{:?}", veggies);
}
fn display_fruit<'a>(food: Food<'a>) {
if let Food::Fruits(first, second, third) = food {
println!("Fruit selection: {}, {}, {}", first, second, third);
} else {
println!("This item is not a fruit.");
}
}
The Special Option Enum
Option<T> is a built-in Rust enum defined in the standard library:
enum Option<T> {
Some(T),
None
}
Key features of Option:
- It's included in Rust's prelude, so no explicit import is needed
- Its variants (Some and None) can be used without the Option:: prefix
- It provides useful methods like is_some(), is_none(), and unwrap()
- It can be used as function parameters
Option Example
fn main() {
let present_value = Some(42); // Type is Option<i32>
let text = Some("Hello Rust"); // Type is Option<&str>
let empty_value: Option<i32> = None; // Type is Option<i32>
println!("Present value: {:?}", present_value);
println!("Text: {:?}", text);
println!("Empty value: {:?}", empty_value);
println!("Is empty: {:?}", empty_value.is_none());
}
</i32></i32></i32>
Matching with Enums
Rust's match expression provides powerful pattern matching for enums:
- Match is exhaustive - all possibilities must be handled
- It supports matching single values, multiple values, and all value
- It uses the underscore (_) to match any value
- Once a match is found, execution stops (no fall-through)
Match Example
#[derive(Debug)]
enum Era{Classic,Modern}
enum Coin{Penny(Era),Nickel,Dime,Quarter,HalfDollar,Dollar}
enum Civilization{Chinese(Era),European,American,African}
fn main(){
let coin = Coin::Nickel;
let dollar = Coin::Dollar;
process_coin(coin);
process_coin(dollar);
let civilization = Civilization::Chinese(Era::Classic);
let european_civ = Civilization::European;
process_civilization(civilization);
process_civilization(european_civ);
}
fn process_coin(coin:Coin){
match coin{
Coin::Penny(Era::Classic) => println!("Classic penny"), // Match specific value
Coin::Penny(_) => println!("Any penny"), // Match all values
Coin::Penny(Era::Modern) | Coin::Penny(Era::Classic) => println!("Any penny variant", // Match multiple values
Coin::Nickel => println!("Nickel"),
Coin::Dime => println!("Dime"),
Coin::Quarter => println!("Quarter"),
other => println!("Other coin: {:?}", other),
_=> println!("Unreachable pattern")
};
}
fn process_civilization(civ:Civilization){
match civ {
Civilization::Chinese(era) => println!("Ancient Chinese culture: {:?}", era),
Civilization::European => println!("Innovative European cultures"),
_ => println!("Other civilization: {:?}", civ)
};
}
The if let Syntax Sugar
Rust provides if let as a concise alternative to match when you only need to match one pattern:
if let Example
#[derive(Debug)]
enum Period{GoldenAge,Modern}
enum Currency{Cent(Period),FiveCents,TenCents,FiftyCents}
fn main(){
let currency = Currency::Cent(Period::GoldenAge);
// Traditional match
match currency{
Currency::Cent(Period::GoldenAge) => println!("Golden age currency?"),
_=>println!("Not a golden age currency")
}
// Using if let
if let Currency::Cent(Period::GoldenAge) = currency{
println!("Golden age currency?");
} else {
println!("Not a golden age currency");
}
// if let without else
if let Currency::Cent(Period::Modern) = currency{
println!("Modern currency");
}
}
Conclusion
Rust enums offer unique capabilities compared to enums in other languages, particularly in their ability to associate different types of data with variants. The exhaustive matching ansures type safety, while features like Option provide a standardized way to handle potentially missing values. The if let syntax offers a convenient shorthand for simple pattern matching scenarios.