After grouping data with structs, the next step is defining the states that data can occupy. A Rust enum represents "exactly one of several forms," and [[match|match]] lets you branch on those forms. [[option|Option]] is a standard enum for "value present or absent." This part upgrades our study log with enums to make state updates clearer.
Enum Basics
enum StudyState {
Planned,
InProgress { percent: u8 },
Done,
}
fn main() {
let state = StudyState::InProgress { percent: 40 };
match state {
StudyState::Planned => println!("Not started"),
StudyState::InProgress { percent } => println!("{}% in progress", percent),
StudyState::Done => println!("Finished"),
}
}
Each variant can carry its own data. [[match|match]] must account for all variants, and you can destructure fields right inside the arms.
Combining Structs and Enums
Let’s extend the earlier StudyLog by storing state as an enum.
enum Progress {
Todo,
Doing(u8),
Done,
}
struct StudyLog {
topic: String,
state: Progress,
notes: Vec<String>,
}
impl StudyLog {
fn new(topic: &str) -> Self {
Self {
topic: topic.to_string(),
state: Progress::Todo,
notes: Vec::new(),
}
}
fn update(&mut self, progress: Progress) {
self.state = progress;
}
fn render(&self) -> String {
let label = match &self.state {
Progress::Todo => "Waiting",
Progress::Doing(p) => {
if *p >= 100 { "Almost done" } else { "In progress" }
}
Progress::Done => "Finished",
};
format!("{} ({})", self.topic, label)
}
}
fn main() {
let mut log = StudyLog::new("enum");
log.update(Progress::Doing(60));
println!("{}", log.render());
log.update(Progress::Done);
println!("{}", log.render());
}
Because match is an expression, you can build strings or numbers directly in an arm and return them. Enums constrain state to the handful of values you care about, so there’s less guessing than with ad‑hoc conditionals.
Representing Value Presence with Option
[[option|Option<T>]] is simply an enum with two variants: Some(T) or None. It replaces null and lets the type system enforce "maybe there’s data." You can imagine it as a library-provided enum, just like the ones you write yourself.
struct StudyLog {
topic: String,
last_note: Option<String>,
}
impl StudyLog {
fn add_note(&mut self, note: &str) {
self.last_note = Some(note.to_string());
}
fn last_note(&self) -> Option<&str> {
self.last_note.as_deref()
}
}
fn main() {
let mut log = StudyLog {
topic: String::from("Option"),
last_note: None,
};
if let Some(text) = log.last_note() {
println!("{}", text);
} else {
println!("No notes yet");
}
log.add_note("Some/None pattern");
println!("Latest note: {}", log.last_note().unwrap());
}
as_deref converts Option<String> into Option<&str> for free. unwrap panics if the value is None, so keep it for quick demos and prefer match or if let elsewhere.
A quick cheat sheet:
- [[match|
match]]: handle all variants exhaustively. if let: drill into one variant and ignore the rest.unwrap: only in tests or tiny examples where absence would be a bug.
Pattern-Matching Tips
- Because
matchmust mention every variant, the compiler warns you when a new variant lacks handling. - The
_pattern groups leftover cases, but overusing it can hide missing logic when enums grow. if letkeeps code short when you only care about a single pattern. Example:if let Progress::Doing(percent) = log.state { ... }
Try It Yourself
- Add a
Blocked(String)variant toProgressand print the reason insiderender. - Sketch how you’d use [[result|
Result]] instead of [[option|Option]] to carry error messages. (We’ll go deep in part 11.) - Practice matching multiple values at once: e.g., inspect
Progress::Doing(p)andnotes.len()at the same time to craft a richer message.
Wrap-Up
Enums plus pattern matching are Rust’s way to express state explicitly. Once you’re comfortable with common enums like Option, you can avoid null pointers entirely and let match ensure you cover every branch. Next we’ll shift to collections like Vec and HashMap and plug our structs and enums into real data sets.
💬 댓글
이 글에 대한 의견을 남겨주세요