Build the mental model
An enum is a type declaring that a value can be exactly one of several possible variants, and Rust's enums are more powerful than a C-style enum of integer constants — each variant can carry its own data, like `enum Difficulty { Basic, Intermediate, Advanced(String) }`. The `match` expression is the primary tool for handling an enum, and the compiler enforces exhaustiveness — forgetting to handle a variant is a compile error, preventing the classic fall-through bug that a `switch` statement can silently allow. Rust has no null pointer concept at all — to avoid what other languages often call the "billion-dollar mistake" (NullPointerException), the standard library provides an `Option<T>` enum (`Some(T)` or `None`) that makes the possibility of "there might not be a value" explicit in the type system, so the compiler never lets you use the underlying value without unwrapping the `Option<T>` first — you are forced, at compile time, to handle the `None` case. Think of it like a postal mailbox: mail might have arrived (Some) or the box might be empty (None) — you have to check before reaching in, and blindly reaching in assuming mail is there (like a null pointer dereference) is exactly the mistake Rust's `Option<T>` forces you to handle at compile time instead.
Connect it to a real scenario
Define the content analyzer's `Difficulty` as `enum Difficulty { Basic, Intermediate, Advanced }`, replacing Lesson 9's `String` field with this enum, entirely eliminating the possibility of a typo like "Bassic" at the type level. When parsing a tag out of a lesson file, a tag might not be found, so use `fn find_tag(text: &str) -> Option<&str>` as the return type — callers must handle it with `match find_tag(text) { Some(tag) => ..., None => ... }`; assuming a tag exists and unwrapping blindly can panic on the None case. Producing a display string from the Difficulty enum via `match` means the compiler only accepts the code once every variant is handled, so adding a new variant (`Expert`) later automatically flags this `match` for an update.
Try the working example
enum Difficulty {
Basic,
Intermediate,
Advanced,
}
fn label(d: &Difficulty) -> &str {
match d {
Difficulty::Basic => "basic",
Difficulty::Intermediate => "intermediate",
Difficulty::Advanced => "advanced",
}
}
fn find_tag(text: &str) -> Option<&str> {
text.split_whitespace().find(|w| w.starts_with('#'))
}
fn main() {
println!("{}", label(&Difficulty::Intermediate));
match find_tag("Rust ownership #systems") {
Some(tag) => println!("tag found: {tag}"),
None => println!("no tag found"),
}
}You print "intermediate" and "tag found: #systems".5-minute try-it
Create `enum ContentKind { Text, Code(String), Diagram }` where the `Code` variant carries a language name — write a function that uses `match` to produce a description string for each variant.
One important caution
Blindly calling `.unwrap()` on an `Option<T>` without handling the None case — only unwrap when you're certain a value exists, or the code can panic at runtime.
Reaching for a `_` wildcard arm in `match` instead of handling every enum variant explicitly — adding a new variant later silently falls into the wildcard, losing the compiler's exhaustiveness check as a safety net.