Rust has no null; Option is how every API says 'maybe there is nothing here', so you use it constantly.
Rust has no null. A value that may be missing has type `Option<T>`, a standard enum with two variants: Some(T) and None. Because Option<i32> and i32 are different types, you cannot accidentally use a maybe-missing value as if it were there; the compiler makes you handle None.
You will meet it everywhere: v.get(i), map.get(&k), s.find('x'), iter.next() all return Option.
Ways to get at the value, from most to least explicit:
match opt { Some(x) => ..., None => ... }handles both cases.if let Some(x) = opt { ... }handles only the case you care about, optionally withelse.let Some(x) = opt else { return; };(let-else) bindsxfor the rest of the function and requires theelseblock to diverge (return, break, continue or panic). It keeps the happy path unindented.- Combinators transform without unpacking:
opt.map(|x| x * 2),opt.unwrap_or(0),opt.unwrap_or_default(),opt.and_then(f),opt.filter(...), andopt.ok_or(err)to convert into aResult.
unwrap() returns the value or panics on None; expect("msg") does the same with a message. They are fine in tests, prototypes, or when an invariant guarantees a value, but not for input that can legitimately be missing.
In the 2024 edition, let chains combine these: if let Some(u) = user && u.active { ... }.
To look inside an Option<String> without moving it, use opt.as_deref() (gives Option<&str>) or match on &opt.
Example
fn parse_port(s: &str) -> Option<u16> {
s.strip_prefix("port=")?.parse().ok() // ? on Option returns None early
}
fn describe(config: &[&str]) -> String {
let Some(first) = config.first() else {
return "empty".to_string(); // else block must diverge
};
if let Some(port) = parse_port(first) && port >= 1024 {
format!("user port {port}")
} else {
String::from("no user port")
}
}
fn main() {
println!("{}", describe(&["port=8080"])); // user port 8080
println!("{}", describe(&["port=80"])); // no user port
println!("{}", describe(&[])); // empty
let name: Option<&str> = None;
println!("{}", name.map(|n| n.len()).unwrap_or(0)); // 0
}Remember
Option<T>replaces null; the type system forces you to handleNoneif letfor one case,let ... elsefor an early exit, combinators for transformationsunwrap/expectpanic onNone; keep them for true invariants and tests
Watch out for
- Calling
.unwrap()on user-controlled data and crashing on the first missing value.