Option and Result¶
One line: Rust has no null and no exceptions — it has two ordinary enums, Option for a value that may be absent and Result for an operation that may fail, and a compiler that will not let you read either one without saying what happens in the other case.
Neither type is built into the language. Both are enums anyone could have written, which is worth knowing early: everything in this section is a method on a normal type, and you can read the source. What the language contributes is match — because it must account for every variant, "I forgot the empty case" stops being a class of bug and becomes a build error.
The bulk of the section is the middle ground between match and .unwrap(). There are a dozen small methods that each answer one question — what if it is missing, what if I want to transform it, what if building the fallback is expensive — and choosing among them is most of what fluency in Rust looks like from the outside.
Those methods have a shape, and it is worth seeing once before you start — nothing below assumes you already know it. Every box is a type, every label is a method that moves you between types, and the two blue boxes in the middle are the pair this section is about:
![Directed graph of the conversions between Option and Result. T, E and the never type sit across the top; Result<T, E> and Option<T> in the middle; arrows labelled with method names - ok, err, ok_or[_else], unwrap, expect, unwrap_or[_else], unwrap_or_default, unwrap_err, expect_err, map, and[_then], map_err, map_or[_else], or[_else], filter, xor, replace - connect them to each other and to Result<T, F>, Result<U, E>, U and Option<U> along the bottom.](../img/option_result_transformations_effective_rust.png)
Figure 1-1, "Option and Result transformations", from Effective Rust ↗ by David Drysdale, © 2024 Galloglass Consulting Limited. Reproduced unmodified under CC BY-NC-ND 4.0 ↗; original drawing ↗.
Red marks the four that can panic. Notice where they land: unwrap and expect reach T through the same box as unwrap_or[_else] and unwrap_or_default, which reach that identical T without ever aborting. Same destination, different answer on the sad arm — and choosing between those two behaviours is most of what the pages below are teaching.
| Lesson | Level | What it teaches |
|---|---|---|
Some and None |
101 | The enum itself: two shapes, one exhaustive match, and why Some(0) is not None |
Some is a constructor, not a flag |
101 → 201 | Why Some(None) is E0308 and not "present but empty" — Some is a fn(T) -> Option<T>, so the argument must be the payload; plus the one type where Some(None) is the right answer |
Option vs Result |
101 | Absence versus failure — and the single question ("could the caller ask why not?") that decides which type you want |
| What a monad is | 301 | The shape Option, Result and Vec all share — and why Rust uses monads without ever saying the word |
if let |
101 | A match with one arm — the family (if let / let … else / while let / matches!), and the exhaustiveness you trade away |
while let |
201 | The loop whose exit condition is a pattern — and the one bug if let cannot have: a body that never makes progress |
| One arm, many values | 101 → 201 | 8 \| 12 \| 18 and 0..=7: the two ways to widen a match arm, the two lints that catch a botched collapse, and the one mistake neither can see |
unwrap_or |
201 | The default you already have — eager, consuming, and it erases the very difference it stood in for |
unwrap_or_else |
201 | The lazy fallback — built only if needed, allowed to consume what it captures, and on a Result the only one handed the error |
unwrap_or_default |
201 | The fallback the type chose — a derived Default is the type's zero, not your domain's, and a missing impl is a guard rail |
map_or and map_or_else |
201 | Transform and fall back in one call — the default written first and run last, and the clippy lint on each side of it |
Transforms instead of match |
201 | The whole vocabulary at once: which sentence picks map, and_then, ok_or or map_err — plus the _else suffix measured, and the as_ref() that E0507 is asking for |
The ? operator |
201 | Unwrap, return, and convert the error through From — three statements in one character, and the third is the one people are surprised by |
expect |
201 | The message is a claim about why this cannot fail — and being unable to write it is the finding |
| What a panic costs | 201 | The other half of unwrap: where the panic points, what unwinding gives back (memory) and what it does not (your work), and why the exit code is 101 |
| Reading a backtrace | 201 | The caller the panic message will not name: switching the frame list on, reading it innermost-first, and the two functions --release inlines out of it |
| Partial functions | 201 | Why Option exists at all: it turns a function that is undefined somewhere into one that always answers |
Returning None on error |
201 | Why input.parse().ok() is usually a downgrade: four distinct causes arriving as one indistinguishable None |
| Zero wins is not zero games | 201 | A guard on the wrong condition: the input with no answer is the one that gets a number, and Result does not stop it |
| Initial values | 201 | The job where Option is usually the wrong tool — Rust lets you declare without initializing and proves you assigned |
| Optional function arguments | 201 | No default parameters, no overloading — the five shapes that replace them, and why Option<&T> beats &Option<T> |
Option fields |
101 | Option in a type definition: required-by-default fields, and when Option<Vec<T>> is right |
Option is a one-item collection |
201 | It iterates; None really is variant 0; and why the tag is often free |
| Nullable pointers | 201 | Rust has no null, so an absent pointer is a different type — Option<Box<T>>, free, and what makes a recursive type possible |
The Result you are reading is probably an alias |
201 | io::Result<T> is Result<T, io::Error> — how to expand an alias and read what can actually go wrong, plus Ok(()) and Infallible |
Shadowing and unwrap |
201 | Two unrelated ideas the tutorials tie together: what keeps the original Option alive is Copy, not the shadow — and what shadowing is really for |
| Six kinds of zero | 201 | Where Option runs out: six reasons a cell is empty, one enum, and a report the compiler audits |
Related sections¶
- Enums — the feature both of these types are made of
- Errors — what a
Resultdoes on its way out of a program somebody else runs - Ownership — why
unwrapon aStringconsumes it and on ani32does not
OPTION.md is the full reading order, and lists the eight jobs the standard library says Option exists to do.
Po polsku¶
W Ruscie nie ma null i nie ma wyjątków. Są za to dwa zwyczajne wyliczenia (enums): Option (w polskim Tour of Rust: Opcja) na wartość, której może nie być, i Result (Rezultat) na operację, która może się nie udać. Warto od razu wiedzieć, że żaden z tych typów nie jest wbudowany w język — to zwykłe wyliczenia, które ktokolwiek mógłby napisać, a każdą metodę z tego działu można po prostu przeczytać w źródłach std. Język dokłada do nich jedną rzecz: match, który musi obsłużyć wszystkie warianty, więc „zapomniałem o przypadku pustym” przestaje być klasą błędów, a staje się błędem kompilacji.
Czytelnikowi przychodzącemu z Javy, C# albo Pythona najbardziej przydadzą się dwa rozróżnienia. Option to nie jest null z ładniejszą nazwą: nie da się go przez pomyłkę użyć jak wartości — najpierw trzeba go otworzyć, a kompilator tego pilnuje. Result to nie jest try/catch: błąd wędruje jako zwracana wartość, więc sygnatura funkcji mówi wprost, co może pójść nie tak, i nic nie przeskakuje nad kodem wywołującym. Polska literatura mówi tu o obsłudze błędów, ale bez mechanizmu wyjątków — cała „obsługa” to zwykłe dopasowanie wzorców do zwróconej wartości.
Reszta działu sprowadza się do jednego pytania: co zrobić w przestrzeni między match a .unwrap(). Diagram powyżej czyta się prosto — każde pudełko to typ, każda etykieta to metoda przenosząca cię między typami, a na czerwono zaznaczone są te cztery, które mogą spanikować. Zwróć uwagę, gdzie lądują: unwrap i expect docierają do dokładnie tego samego T, co unwrap_or[_else] i unwrap_or_default, tylko te drugie robią to, nie przerywając programu. Ten sam cel, inna odpowiedź na smutnej gałęzi — i wybór między tymi dwoma zachowaniami to jest właśnie to, czego uczą kolejne strony z tabeli.
Szukaj po polsku: obsługa błędów w Ruscie · wyliczenia Opcja i Rezultat · rust Option vs Result · rust error handling without exceptions · rust unwrap_or_else