Search⌘ K
AI Features

Optional Results

Explore how to use Rust's Option enum to manage optional data safely and idiomatically. Understand handling exhaustive pattern matching for enums, and practice implementing functions that return optional results without errors.

We'll cover the following...

The Loch Ness monster lives in Scotland. Dracula lives in Transylvania. But no one knows where Bigfoot or aliens live. Let’s write some code for this:

Rust 1.40.0
enum Monster {
LochNess,
Dracula,
Bigfoot,
Alien,
}
enum Place {
Scotland,
Transylvania,
}
impl Monster {
fn lives(&self) -> Place {
use Monster::*;
use Place::*;
match self {
LochNess => Scotland,
Dracula => Transylvania,
}
}
}
fn main() {} // dummy

This won’t compile. We haven’t handled the cases of bigfoot or aliens. The compiler tells us this:

error[E0004]: non-exhaustive patterns: `&Bigfoot` and `&Alien` not covered
  --> src/main.rs:20:15
   |
2  | / enum Monster {
3  | |     LochNess,
4  | |     Dracula,
5  | |     Bigfoot,
   | |     ------- not covered
6  | |     Alien,
   | |     ----- not covered
7  | | }
   | |_- `Monster` defined here
...
20 |           match self {
   |                 ^^^^ patterns `&Bigfoot` and `&Alien` not covered
   |
   = help: ensure that all possible cases are being handled, possibly by adding wildcards or more match arms

We need to give some response for those other monsters. One possibility would be to add an Unknown variant to Place:

Rust 1.40.0
enum Place {
Scotland,
Transylvania,
Unknown,
}
match self {
LochNess => Scotland,
Dracula => Transylvania,
Bigfoot => Unknown,
Alien => Unknown,
}

But this doesn’t feel quite right. Unknown isn’t a place like Scotland. We’re mixing up two different concepts. Instead, the Rust way to handle this would be with an extra wrapper enum type, like this:

Rust 1.40.0
enum Place {
Scotland,
Transylvania,
}
enum OptionalPlace {
Known(Place),
Unknown,
}
impl Monster {
fn lives(&self) -> OptionalPlace {
use Monster::*;
use Place::*;
use OptionalPlace::*;
match self {
LochNess => Known(Scotland),
Dracula => Known(Transylvania),
Bigfoot => Unknown,
Alien => Unknown,
}
}
}
enum Monster {
LochNess,
Dracula,
Bigfoot,
Alien,
}
fn main() {} // dummy

Cool, that works. Now let’s talk about the food our monsters eat. Dracula eats blood, and aliens eat cows (I guess). But we don’t know about the other two monsters. We can write up two new enums to help:

Rust 1.40.0
enum Food {
Blood,
Cows,
}
enum OptionalFood {
Known(Food),
Unknown,
}

But our OptionalFood and OptionalPlace look really similar! It would be nice if there was some way to unify them into a single type. Thinking back to type parameters, we can try using a type parameter and creating an Optional enum:

Rust 1.40.0
enum Optional<T> {
Known(T),
Unknown,
}
fn lives(&self) -> Optional<Place> {
use Monster::*;
use Place::*;
use Optional::*;
match self {
LochNess => Known(Scotland),
Dracula => Known(Transylvania),
Bigfoot => Unknown,
Alien => Unknown,
}
}

In fact, this kind of thing is so common in Rust, that the std crate and its prelude provide an enum for this purpose out of the box. It’s called Option, and it has two variants: Some (like our Known) and None (like our Unknown). Rewriting our lives method to use it makes our code more idiomatic (or normal) Rust:

Rust 1.40.0
impl Monster {
fn lives(&self) -> Option<Place> {
use Monster::*;
use Place::*;
match self {
LochNess => Some(Scotland),
Dracula => Some(Transylvania),
Bigfoot => None,
Alien => None,
}
}
}
enum Monster {
LochNess,
Dracula,
Bigfoot,
Alien,
}
enum Place {
Scotland,
Transylvania,
}
fn main() {}

NOTE: Astute readers may be asking, why did we get to say Some instead of Option::Some? There’s no use Option::*; statement! The answer is that Option, Some, and None are such common parts of Rust programming that the prelude automatically adds the use for all three of them.

Exercise

Implement the eats method so that the program below succeeds. Don’t modify the main function.

Rust 1.40.0
enum Monster {
LochNess,
Dracula,
Bigfoot,
Alien,
}
#[derive(PartialEq, Eq, Debug)]
enum Food {
Blood,
Cows,
}
impl Monster {
// FIXME add eats here
}
fn main() {
use Monster::*;
use Food::*;
assert_eq!(Dracula.eats(), Some(Blood));
assert_eq!(Alien.eats(), Some(Cows));
assert_eq!(Bigfoot.eats(), None);
assert_eq!(LochNess.eats(), None);
}