| title | slug |
|---|---|
Enums |
enums |
- An enum is a single type that contains variants, which represent the possible values of the enum at any given time.
- By convention, the enum name and its variants' names should follow
PascalCase. - Can access the variants using the
::notation and the variant name. ex. Day::Sunday
enum Day {
Sunday,
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday,
}
// 💡 Day is the enum. Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday are its variants.- An enum variant can have either,
- No data (a unit variant)
- Unnamed ordered data (a tuple variant)
- Named data/ fields (a struct variant)
enum FlashMessage { Success, // 💡 A unit variant (no data) Error(u8, String), // 💡 A tuple variant (one or more , separated data) Warning { field: String, message: String }, // 💡 A struct variant (one or more , separated name: value data) } // 💡 FlashMessage is the emnum, Success, Error, Warning are its variants.
💡 In Rust, the term "instantiation" is used to describe the act of creating a concrete instance of a type (struct or enum).
💡 In Rust, the term "field" is used to describe a named component in a C-like struct & struct-like enum variant, and the term "element" is used to describe an unnamed component in a tuple struct & tuple-like enum variant. The term "member" is used to describe both.
💯 More complex examples can be found on Generics, Impls and Traits, Lifetimes and Modules sections.
#![allow(unused)] // 💡 skip unused warnings, as we don't read fields in the enums
#[derive(Debug)]
enum FlashMessage { // Definition
Success,
Error(u32, String),
Warning { field: String, message: String },
}
fn main() {
// 1. Instantiation with separate variable declaration and assignment
let x: FlashMessage; // Declaration with the data type
x = FlashMessage::Success;
println!("{x:?}"); // Success
// 2. Instantiation with a direct variable initialization
let a = FlashMessage::Success;
let b = FlashMessage::Error(401, "Unauthorized".to_string());
let c = FlashMessage::Warning { field: "email".to_string(), message: "This is required".to_string() };
println!("{a:?}"); // Success
println!("{b:?}"); // Error(401, "Unauthorized")
println!("{c:?}"); // Warning { field: "email", message: "This is required" }
}// 3. Instantiation with a default variant
#![allow(unused)] // 💡 skip unused warnings, as we don't use the all variants of the enum
#[derive(Debug, Default)]
enum Hand {
Left,
#[default] // 💡Set Right as the default variant
Right,
}
fn main() {
let a = Hand::default(); // Instantiation with the default variant
println!("{a:?}"); // Right
}In Rust, the #[derive()] attribute is used to automatically generate an implementation of certain traits for a custom data structure (struct and enum), instead of you writing them by hand. The std::fmt::Debug trait allows us to format a value with {:?} or {:#?} in println! and similar macros. The std::default::Default trait allows us to create a new instance of a type with the Type::default() method.
#![allow(unused)] // 💡 skip unused warnings, as we don't use the all variants of the enum
enum Season {
Spring,
Summer,
Autumn,
Winter,
}
fn main() {
let a = Season::Winter;
let result = match a {
Season::Spring => "☀️",
Season::Summer => "🍁",
Season::Autumn => "🍂",
Season::Winter => "❄️",
};
println!("{result}"); // ❄️
}if let is useful when we only care about handling one (or few) specific patterns and don’t need to explicitly match every possible case.
#![allow(unused)] // 💡 skip unused warnings, as we don't use the all variants of the enum
enum Season {
Spring,
Summer,
Autumn,
Winter,
}
fn main() {
let a = Season::Winter;
let result = if let Season::Spring = a {
"☀️"
} else if let Season::Summer = a {
"🍁"
} else if let Season::Autumn = a {
"🍂"
} else if let Season::Winter = a {
"❄️"
} else {
unreachable!()
};
println!("{result}"); // ❄️
}In Rust, directly accessing an enum variant's fields without any form of pattern matching is not possible. We need to use pattern matching to access the fields by using a match expression or if let expression.
#![allow(unused)] // 💡 skip unused warnings, as we don't use the all variants of the enum
enum FlashMessage {
Success,
Error(u32, String),
Warning { field: String, message: String },
}
fn main() {
let a = FlashMessage::Error(401, "Unauthorized".to_string());
let result = match a {
FlashMessage::Success => "We'll get back to you.".to_string(),
FlashMessage::Error(_, msg) => msg, // 💡 Destructuring only the second element of the tuple variant.
FlashMessage::Warning { message, .. } => message, // 💡 Destructuring only the second field of the struct variant.
};
println!("{result}"); // Unauthorized
}if let is useful when we only care about handling one (or few) specific patterns and don’t need to explicitly match every possible case.
#![allow(dead_code)] // 💡 Remove dead_code warnings, as we don't access the all elements of variants.
enum FlashMessage {
Success,
Error(u32, String),
Warning { field: String, message: String },
}
fn main() {
let a = FlashMessage::Error(401, "Unauthorized".to_string());
if let FlashMessage::Error(_, msg) = a {
println!("{msg}"); // Unauthorized
} else if let FlashMessage::Warning { message, .. } = a {
println!("{message}");
} else {
println!("We'll get back to you.");
}
}