How it works
The explanation, with examples you can run.
Data in variants, and exhaustive match
#[derive(Debug)]enum Shape { Circle { r: f64 }, Rect { w: f64, h: f64 }, Point,} fn area(s: &Shape) -> f64 { match s { // must cover every variant, or it does not compile Shape::Circle { r } => std::f64::consts::PI * r * r, Shape::Rect { w, h } => w * h, Shape::Point => 0.0, }} fn parse_port(s: &str) -> Option<u16> { let n: u16 = s.parse().ok()?; // None if it is not a number if n == 0 { None } else { Some(n) }} fn main() { for s in [Shape::Circle { r: 1.0 }, Shape::Rect { w: 2.0, h: 3.0 }, Shape::Point] { println!("{s:?} -> {:.2}", area(&s)); } let Some(port) = parse_port("8080") else { panic!("bad port"); }; if let Some(p) = parse_port("443") && p < 1024 { // let chains: Rust 1.88+, edition 2024 println!("{port} and privileged {p}"); } println!("{:?} {:?}", parse_port("0"), parse_port("http"));}Output: three lines of areas (3.14, 6.00, 0.00), then 8080 and privileged 443, then None None.
In memory an enum is a tag plus enough space for its largest variant. The compiler often hides the tag in spare bits: Option<&T> and Option<Box<T>> are the same size as a plain pointer.
Patterns
Patterns appear in match, let, if let, while let, for and function parameters. They can:
- destructure structs, tuples, enums and slices (
[first, .., last]); - bind parts to names, by value or with
ref, and withname @ patternto keep the whole; - match ranges and alternatives (
1..=9,'a' | 'e'); - add guards:
Some(n) if n > 10 =>. Since Rust 1.95 guards can also beif let.
The compiler checks that a match is exhaustive and warns about arms that can never be reached.
Option instead of null
Rust has no null. An absent value is None, and to use the value inside a Some you must say what happens otherwise: match, if let, unwrap_or(default), map, and_then, or ? to return None early, as parse_port does. unwrap() and expect("why") are there for when absence really is a bug; they panic.
One pattern at a time
if let Some(x) = opt { ... }handles one variant and ignores the rest.let Some(x) = opt else { return ... };(Rust 1.65) binds or bails out, keeping the main path unindented.- Let chains (Rust 1.88, 2024 edition only):
if let Some(p) = a && let Ok(v) = p.parse::<u32>() && v > 3 { ... }combines patterns and conditions without nesting.
Prefer an enum over a set of boolean flags or a struct with optional fields that are only valid in some combinations: if a state can't be represented, it can't occur.
Check yourself
3 questions. Get them all right to finish this topic.
0 of 3 right so far. Get all three to finish this topic.