Skip to content
GitHub
ReferenceOptionals

Optionals

T? instead of null - some and none, ??, !, ?., comparing, if let and let-else.

All 5 examples tested by CIEdit this pageReport a problem

There’s no null. A value that may be absent is a T?, which is either none or some(x):

Tested
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)}")
}
  • some(x) holds x. none holds nothing, and takes its type from where it’s used: let n: i32? = none.
  • A T never turns into a T? by itself, so return n in a function returning i32? doesn’t compile. Write return some(n).
  • An optional can’t hold another optional, or (). Generic code is the exception: a T? whose T is an optional holds one (generics).
Doesn't compile, on purpose
fn half(n: i32) -> i32? {
if n % 2 == 0 {
return n / 2
error: write some(...)
}
return none
}
fn main() {
let a = half(4)
let b: i32 = a
error: unwrap it with ??, !, if let or let ... else
let d = none
error: can't tell what kind of optional none is
println("{b} {d}")
}
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, b is a T or a T?. With a T the result is a T. With a T? it’s a T?, so a ?? b ?? 0 tries a, then b, 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.
Tested
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}")
}

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.

Tested
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"))}")
}

== 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).

Tested
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)}")
}

Besides functions that return them: T.try_from(x), the checked_ methods, and fields declared T?, which can default to none.

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.