Optionals
T? instead of null - some and none, ??, !, ?., comparing, if let and let-else.
There’s no null. A value that may be absent is a T?, which is either none or some(x):
fn half(n: i32) -> i32? { if n % 2 == 0 { return some(n / 2) } return none}
fn quarter(n: i32) -> i32 { let h = half(n) else return -1 // unwrap, or leave return half(h) ?? -2 // unwrap, or use a default}
fn main() { if let h = half(12) { // unwrap for one block println("half is {h}") } println("{quarter(8)} {quarter(6)} {quarter(5)}")}half is 6 2 -2 -1
Making one
Section titled “Making one”some(x)holdsx.noneholds nothing, and takes its type from where it’s used:let n: i32? = none.- A
Tnever turns into aT?by itself, soreturn nin a function returningi32?doesn’t compile. Writereturn some(n). - An optional can’t hold another optional, or
(). Generic code is the exception: aT?whoseTis an optional holds one (generics).
fn half(n: i32) -> i32? { if n % 2 == 0 { return n / 2error: write some(...) } return none}
fn main() { let a = half(4) let b: i32 = aerror: unwrap it with ??, !, if let or let ... else let d = noneerror: can't tell what kind of optional none is println("{b} {d}")}Reading one
Section titled “Reading one”| Written | Gives |
|---|---|
if let x = opt { } else { } |
Runs the first block with x bound to the value, or the else (if let) |
let x = opt else <leave> |
Binds x, or runs the else, which must leave: return, break, continue or a panic (let ... else) |
opt ?? b |
The value, or b if there’s none. b runs only when it’s needed |
opt! |
The value, or a panic: unwrapped \none` with `!`` |
opt?.field |
some of the field, or none |
match opt { some(x) => ..., none => ... } |
Either, with patterns inside some (patterns) |
- In
a ?? b,bis aTor aT?. With aTthe result is aT. With aT?it’s aT?, soa ?? b ?? 0triesa, thenb, then gives 0. ??next to any other binary operator needs parentheses:(a ?? 0) + 1(strictness).?.on a field that’s already an optional gives that optional, not one wrapped twice.- Reading a field of an optional without
?.or unwrapping is an error.
struct Player { name: str target: i32? = none}
fn calls(n: i32) -> i32 { println("calls {n}") return n}
fn main() { let p: Player? = some(Player { name: "Steve", target: some(3) }) let gone: Player? = none println("{p?.target ?? 0} {gone?.target ?? 0} {p!.name}") let a = some(2) ?? calls(1) let b = gone?.target ?? calls(5) println("{a} {b}")}3 0 Steve calls 5 2 5
Taking the value out
Section titled “Taking the value out”A value can’t move out of a field of a mut view, since the caller still owns it, or out of a list by indexing, which would leave a hole (ownership). An optional can give its value up instead:
| Written | Does |
|---|---|
opt.take() |
Moves the optional out whole, and leaves none |
opt.replace(x) |
Moves the optional out whole, and leaves some(x) |
Both change opt, so it has to be a var, a mut view or a part of one. The value moves rather than being copied, so nothing is freed twice: what was there is the result’s to free, and the place holds something new.
struct Encoder { key: List<u8>}
struct Connection { encoder: Encoder?}
// Lends the encoder out and back, through a view of the connection.fn lend(c: mut Connection) -> usize { let e = c.encoder.take() else return 0 let n = e.key.len() c.encoder = some(e) return n}
fn main() { var c = Connection { encoder: some(Encoder { key: [1, 2, 3] }) } println("{lend(mut c)}") var slots: List<String?> = [some(String("p")), none] let p = slots[0].take() let was = slots[1].replace(String("q")) println("{p ?? String("-")} {was == none} {slots[0] == none} {slots[1] == some(String("q"))}")}3 p true true true
Comparing
Section titled “Comparing”== and != compare two optionals: they’re equal when both are none, or both hold equal values. In a string, an optional shows as some(3) or none (comparing and printing). An optional compares only with another optional, so it’s opt == some(3), not opt == 3. A float inside compares as floats do, so some(NaN) doesn’t equal itself. < and the other orderings put none before any value (comparing and printing).
fn main() { let n: i32? = none let three: i32? = some(3) println("{n == none} {three == some(3)} {three != n}") println("{n < three} {three < some(4)}")}true true true true true
Where optionals come from
Section titled “Where optionals come from”Besides functions that return them: T.try_from(x), the checked_ methods, and fields declared T?, which can default to none.
In memory
Section titled “In memory”A T? is a T followed by a bool saying whether it’s there, so it costs no allocation and no indirection. opt.map(f) and opt.or(x) come with closures and generics.