The Option map¶
Level: reference · the map
One line: Option<T> is either Some(T) or None, and this page is the door to every lesson about it — in the order the questions actually come up.
There is a lot here, because Option is the type you meet on your first day and are still learning in your third month. The lessons are all one-idea pages that stand alone; what follows is a reading order rather than a syllabus, so start where your question is. If you just want to know what Option is, read the next section — the tables after it are the reading order, not the explanation.
What it is, before the reading order¶
If you have landed here wanting the idea rather than the syllabus, it is this.
Rust has no null. A variable of type i32 is a number — not "usually a number, sometimes null". Always. So how do you write a function that sometimes has no answer? find_user() when nobody has that id; parse() on garbage; "what score did this ballot give Cara?" when the ballot left her blank.
You change the type. Not i32, but Option<i32> — an ordinary enum with exactly two shapes:
So an Option<i32> is a box that either holds a number or is empty. Some(5) is a full box; None is an empty one. And the part that makes it worth anything: the box is not a number.
fn find_score(name: &str) -> Option<i32> {
if name == "Ada" { Some(5) } else { None }
}
let s = find_score("Ben");
println!("{}", s + 1); // does not compile
error[E0369]: cannot add `{integer}` to `Option<i32>`
--> opt.rs:6:22
|
6 | println!("{}", s + 1);
| - ^ - {integer}
| |
| Option<i32>
You cannot add to it, print it as a number, or pass it where an i32 is wanted. To use the number you have to open the box, and opening it means saying, right there, what happens when it is empty:
Delete the None arm and the program does not build. That is the whole feature. Everything below on this page — unwrap_or, if let, ?, map — is library convenience written on top of that match.
The same idea in the languages you already have¶
- Python. You write this already, in the docstring: "returns an int, or
Noneif not found". Thenscore = find_score("Ben")followed byscore + 1runs fine for months and raisesTypeErrorat 3am. Python'sNoneis a value that can turn up anywhere, and checking for it is your discipline; Rust'sNoneis a shape the type declares, and checking for it is the compiler's job. Same idea, moved out of the comment and into the type — and out of runtime into build time. - ABAP. After
SELECT SINGLE name … INTO lv_name,lv_nameis''— which means both "no row found" and "the row exists and the name is genuinely blank". Which one it was lives in a separate variable,sy-subrc, that you must remember to read and that the next statement overwrites.Option<T>issy-subrcwelded onto the data. It travels with the value everywhere the value goes, nothing overwrites it, and you cannot reach the data without going through it.
The one trap that catches everybody¶
Some(0) is not None. Zero is an answer; None is the absence of one.
Some(0).unwrap_or(42) // -> 0 the voter scored her zero
None.unwrap_or(42) // -> 42 the voter did not score her at all
Python's score or 42 answers 42 for both, because 0 is falsy. ABAP's IS INITIAL says the same, because "0" and "never set" are one bit pattern. Rust keeps them apart — the difference between a ballot that scored a candidate 0 and one that left them blank.
That is enough to read any of the pages below. Some and None is the same ground taken slowly, with the compiler errors in full.
Start here¶
Three pages, in this order, are enough to use Option for real:
| # | Lesson | Level | The question it answers |
|---|---|---|---|
| 1 | Some and None: reading an Option |
101 | What the type is, why match has to cover both shapes, and why Some(0) is not None |
| 2 | if let: one arm, and move on |
101 | Handling only the case you care about — and the exhaustiveness you trade away for it |
| 3 | Option vs Result |
101 | Absence versus failure, decided by one question: could the caller ask why not? |
And the trap that follows the first of those: Some is a constructor, not a flag (101 → 201) — Some(x) is a function call whose argument is the payload, so Some(None) is a type error rather than "present but empty".
And, much later, the name for the shape all of this has: What a monad is, and why Rust never says the word (301) — a wrapper plus a way to chain operations that return the same wrapper, which is what Option, Result and Iterator have in common.
Getting the value out¶
Eight ways, and the last two are decisions rather than accesses:
| Lesson | Level | Reach for it when |
|---|---|---|
unwrap_or: the default you already have |
201 | The fallback is a value you are holding — and the trap that it is computed either way |
unwrap_or_else: the fallback that is built only if it is needed |
201 | The fallback costs something, or depends on the error |
unwrap_or_default: the fallback the type chose for you |
201 | 0 / "" / empty is genuinely the right answer — and when the type's zero is not your domain's |
map_or and map_or_else: transform, or fall back |
201 | You want to change the value on the way out, with a default for the gap |
Option is a one-item collection |
201 | Iterating it, take()/replace(), and asking a question without unwrapping |
while let: loop while the shape holds |
201 | Draining something until it hands back None |
expect: writing down the proof |
201 | Absence is a bug, and you can write the sentence saying why it cannot happen |
| What a panic costs | 201 | Before you write unwrap: what the crash does to a running program |
And one page about a claim you will read elsewhere: Shadowing and unwrap (201) — they are unrelated, and the popular explanation credits shadowing for something Copy is doing.
Why the type exists, and where it belongs¶
| Lesson | Level | What it teaches |
|---|---|---|
Partial functions: why Option exists |
201 | The justification: returning Option<T> makes a function that is undefined somewhere total |
Option fields: modelling what may be absent |
101 | Option in a type definition — required-by-default fields, and when Option<Vec<T>> is right |
| Optional function arguments | 201 | No default parameters and no overloading: the five shapes that replace them |
| Nullable pointers | 201 | Option<Box<T>> — free, and what makes a recursive type possible |
Initial values: when Option is the wrong tool |
201 | The job it looks made for and isn't: Rust lets you declare without initializing and proves you assigned |
When Option is not enough¶
None says "no answer" and nothing more. Once the caller could act on why, you owe them a Result:
| Lesson | Level | What it teaches |
|---|---|---|
Returning None on error |
201 | Why input.parse().ok() is usually a downgrade: four distinct causes arriving as one None |
| Zero wins is not zero games | 201 | Returning Result does not mean you guarded the input that actually has no answer |
The Result you are reading is probably an alias |
201 | io::Result<T> is Result<T, io::Error> — how to expand one and read what can fail |
| Six kinds of zero | 201 | Two variants are not enough either: when a value can be missing for six different reasons, the enum you write instead makes the compiler keep them apart |
The two types also share most of their method surface, and Option vs Result carries a diagram of the whole of it — every conversion between T, E, Option<T> and Result<T, E> on one page. It is the fastest way to see that the crossing is only three methods wide: ok down to Option<T> and err down to Option<E>, with ok_or[_else] the one way back up.
Past that point the topic is no longer Option: how a failure travels out of a program is Errors, whose pages are stubs for now.
The eight jobs the standard library says it does¶
The std::option module docs ↗ list what Option is for, which is a better map of the topic than any tutorial ordering. That list is kept in 01_Foundations with the page covering each job — including the ones a first reading of the docs makes sound alike.
Practising it¶
Most of the pages above end in a ## Practice exercise with a compiled solution. The order to attempt them in is in KATAS.md; every kata is a row there, and the numbers live only in that table.
Looking a term up¶
GLOSSARY.md defines the vocabulary these pages use — and_then, discriminant, exhaustiveness, let … else, niche, the null-pointer optimization, #[must_use], ? — and every entry links to the page that explains it properly.
Po polsku¶
Option<T> to po polsku najczęściej „opcja" (tak tłumaczy to Tour of Rust) albo opisowo „wartość opcjonalna"; w kodzie zostają Option, Some i None, bo to nazwy z biblioteki standardowej. Ta strona jest mapą — wymienia wszystkie lekcje o tym typie w kolejności, w jakiej pytania faktycznie się pojawiają.
Jedno zdanie warto wynieść przed resztą: Option nie jest „rustowym null-em", tylko jego przeciwieństwem. null mieści się w każdym typie wskaźnikowym i nic o sobie nie mówi; Option<T> jest osobnym typem, więc kompilator wie, że wartości może nie być, i nie pozwala o tym zapomnieć. Dlatego nie ma tu „sprawdzania na wszelki wypadek" — albo masz T, albo masz Option<T> i musisz zdecydować, co zrobić z None.
Dla szukających po polsku jedna pułapka porządkowa: większość materiałów omawia Option razem z Result pod wspólnym hasłem „obsługa błędów", co myli, bo None nie jest błędem — to zwyczajny brak wartości, bez informacji o przyczynie. Kiedy przyczyna ma znaczenie, właściwym typem jest Result.
Szukaj po polsku: typ Option · wartość opcjonalna · Some i None · rust Option vs null · rust Option vs Result