Rust Enums
An enum is a type whose value is exactly one of a few named kinds, called variants. Each variant can carry its own data.
Name the kinds with enum
Each value of the enum is exactly one of the named kinds, and variants live under the enum name:
#[derive(Debug)]
enum Light { Red, Yellow, Green }
let light = Light::Green;
println!("light = {:?}", light);Variants can carry data
A variant may carry data: no data, a tuple of values, or named fields. A variant with data works like a small constructor — it builds a value and stores the data inside it:
#[derive(Debug)]
enum IpAddr { V4(u8, u8, u8, u8), V6(String) }
let home = IpAddr::V4(127, 0, 0, 1);
println!("home = {:?}", home);match handles every variant
Use match to handle every variant — the compiler checks that none is forgotten:
enum Direction { Left, Right }
let turn = Direction::Left;
let word = match turn { Direction::Left => "turn left", Direction::Right => "turn right" };
println!("{}", word);Option: a value, or none
Option<T> is Rust's built-in enum for "maybe a value": Some(value) or None. Some and None are so common that they are written bare, with no Option:: in front:
let found: Option<i32> = Some(42);
let missing: Option<i32> = None;
// let total = found + 5; // error: an Option is not a number
println!("found = {:?} missing = {:?}", found, missing);Take the value out with unwrap_or
unwrap_or takes the value out of an Option, with a fallback when it is None:
let missing: Option<i32> = None;
let present: Option<i32> = Some(7);
println!("{} {}", missing.unwrap_or(0), present.unwrap_or(0));Option<&T> takes no extra room
Rust packs Option<&T> tightly: it takes exactly as much room as the &T itself, measured here with std::mem::size_of. The None case hides in the empty address 0, which a real reference can never hold, so no extra tag is stored:
println!("Option<&i32> = {} bytes", std::mem::size_of::<Option<&i32>>());
println!("&i32 = {} bytes", std::mem::size_of::<&i32>());Example
fn divide(a: f64, b: f64) -> Option<f64> {
if b == 0.0 { None } else { Some(a / b) }
}
fn main() {
println!("{:?}", divide(10.0, 2.0));
println!("{:?}", divide(10.0, 0.0));
println!("{}", divide(10.0, 0.0).unwrap_or(f64::INFINITY));
}Key points
- An Option is a different type from the value inside: the value must be taken out first.
- None alone gives Rust no clue about the type, so the type must be named.
- A struct, in contrast, holds all of its fields at once.
In this example, divide returns an Option<f64>. If the second number is 0.0, division is impossible, so it returns None. Otherwise it returns Some of the answer. The first call prints Some(5.0). The second prints None — no crash, no fake answer. The third line shows one way to handle None. unwrap_or steps in with a fallback value, here infinity. It prints inf. The caller always chooses what "no value" means.
Note
Rust has no null.
"No value" is written as None, and the type forces every caller to deal with it.
That is why Rust programs do not crash with null-pointer errors.
Try it Yourself
Edit the code, then press Run.
Running is simulated · Rust Playground
Exercise
How does Rust represent "no value"?