Right, so you’ve got an Option<T>. It’s either Some(value) or None. Great. Now what? You can’t just assume it’s Some and blithely call .unwrap() everywhere—that’s the equivalent of assuming your parachute packed itself and jumping out of the plane. You need to handle both cases. And in Rust, the gold-standard, no-excuses way to do that is with match.

match is our exhaustiveness-checking superhero. The compiler forces you to account for every possible variant of an Option. It won’t let you compile your code if you’ve only handled Some and forgotten about the gaping void of None. This is Rust’s number one strategy for eliminating null pointer exceptions at compile time, and it’s glorious.

Here’s the simplest, most fundamental way you’ll use it:

let some_value: Option<i32> = Some(42);
// let some_value: Option<i32> = None; // Try uncommenting this line to see the other path.

let description = match some_value {
    Some(x) => format!("The meaning of life is definitely {}", x),
    None => "There is no meaning here. It's None.".to_string(),
};

println!("{}", description);

Run that. Then uncomment the None line and run it again. See? Two distinct paths, both handled. The compiler is happy, and your program doesn’t panic at runtime because it encountered a None it didn’t expect. This is the bedrock.

When You Actually Want to Panic

Look, sometimes you do want to panic. Maybe you’re writing a quick script, maybe you’re absolutely certain a value should be there, and if it’s not, the entire world is so broken there’s no point in continuing. Fine. I get it. But even here, match gives you a cleaner way to do it than .unwrap() because you can provide a useful error message.

.unwrap() just panics with a generic “called Option::unwrap() on a None value”. It’s useless. Be a better programmer than that.

let config_file = std::fs::File::open("important_config.toml").ok(); // .ok() turns Result into Option

match config_file {
    Some(file) => { /* do things with the file */ },
    None => panic!("Hey man, I cannot run without 'important_config.toml'. Where is it?!"),
}

This panic message tells you exactly what went wrong and why. It’s a small courtesy for your future self. That said, for these “it should never be None” scenarios, expect() is often more idiomatic and concise: let file = config_file.expect("config file must exist");.

Ignoring the None Case (Safely)

Sometimes, you genuinely don’t care about the None case. You just want to do something with the value if it exists and move on. You could use match, but it’s verbose:

let maybe_name: Option<String> = Some("Ferris".to_string());

match maybe_name {
    Some(name) => println!("Hello, {}!", name),
    None => (), // Do absolutely nothing
}

That None => () arm feels like boilerplate. It’s noise. Rust has a better tool for this: if let. It’s essentially sugar for a match that only cares about one variant.

if let Some(name) = maybe_name {
    println!("Hello, {}!", name);
}
// If it's None, this block is just skipped. Clean and intentional.

Use if let when the None case means “carry on with your life.” It makes your intent clear and keeps the code tight.

The Power of Exhaustiveness in Enums

This exhaustiveness checking isn’t just for Option<T>. It’s for every enum you’ll ever create. This is the real magic. Let’s say you add a third state to your data model weeks after writing all your match statements.

enum ConnectionState {
    Connected,
    Disconnected,
    // ...later you add:
    Connecting(String), // With a progress message
}

The moment you add that new variant, every single match you’ve already written on ConnectionState will suddenly cause a compiler error. It will point you to every place in your code that now needs to handle this new possibility. It’s not a suggestion; it’s a requirement. This turns a potential runtime bug (where you forgot to handle a new state) into a compile-time error. It’s like having a brilliant, hyper-vigilant code review partner who never sleeps. This feature alone has saved me from countless silly mistakes. You learn to trust the compiler, and that trust is well-placed.