Option<T> is a type that represents presence or absence without using null.
Tony Hoare, who introduced null references in ALGOL W, called his creation “the billion dollar mistake”. In many languages, a null reference or pointer can be mistaken for a valid one and cause an exception or invalid access if we forget to check it.
Safe references and ordinary types in Rust do not support null as a valid value. Raw pointers can be null, but they are used outside these guarantees and are common mostly in interoperability.
If a variable is of type String, I guarantee it contains a valid string. If you want to express that there could be nothing, Rust forces you to wrap it in a special type: Option.
What is Option?
Option<T> is an Enum defined in the standard library that has only two variants:
enum Option<T> {
Some(T), // Contains a value of type T
None, // Contains nothing (equivalent to null, but safe)
}It is included in the prelude, so you can use both Some and None directly without importing anything.
The big difference: type safety
Imagine you have an integer i32. In Rust, that number is ALWAYS valid.
If you want an optional number, you have an Option<i32>.
The key is this: they are not the same type. You cannot accidentally sum them.
let x: i8 = 5;
let y: Option<i8> = Some(5);
// ❌ ERROR: cannot add i8 + Option<i8>
// let suma = x + y;The compiler tells you: “Hey, y could be None. I don’t know how to add ‘nothing’ to 5. First make sure there’s data inside”.
This prevents directly confusing absence with value. We can still cause a panic if we force a None with unwrap, but the empty case is visible in the type.
Creating Option values
It is very easy to instantiate this enum:
fn main() {
let some_number = Some(5);
let some_text = Some("Hello");
// If it's None, we must specify the type, because Rust cannot infer it
let absent_number: Option<i32> = None;
}Extracting the value
You have a box (Option) and you want the gift inside. How do you get it out? We have several strategies depending on how much risk or control we want.
The exhaustive way: match
It is the safest and most explicit way. You handle both cases.
fn greet(name: Option<&str>) {
match name {
Some(n) => println!("Hello, {}!", n),
None => println!("Hello, stranger."),
}
}The risky way: unwrap and expect
Just like with Result, these methods give you the value if it exists, or cause a panic! if it’s None.
let x = Some(10);
let value = x.unwrap(); // 10
let empty: Option<i32> = None;
// let value = empty.expect("There should have been a number!"); // 💥 PanicOnly use this if you are 100% sure it is not None.
A default value with unwrap_or
“Give me the value, and if there is nothing, give me this default value.” This is one of the most useful and clean ways.
let x = Some(10);
let y = None;
// If there is data, use it; otherwise, use 0
let a = x.unwrap_or(0); // 10
let b = y.unwrap_or(0); // 0There is also unwrap_or_else(|| expensive_calculation()), which accepts a closure to calculate the default value only if necessary (lazy evaluation).
The flow control shortcut: if let
If you only care about the Some case and want to ignore None:
if let Some(value) = get_configuration() {
println!("Configuration loaded: {}", value);
}Transforming Option with combinators
Rust’s functional style fits very well with Option.
Imagine you have an Option<i32> and, if it has a value, you want to multiply it by 2. If it’s None, it should remain None.
Imperative way (boring):
let x = Some(5);
let result;
match x {
Some(i) => result = Some(i * 2),
None => result = None,
}Functional way (elegant):
The .map() method applies a function to the content if it’s Some, and does nothing if it’s None.
let x = Some(5);
let result = x.map(|i| i * 2); // Returns Some(10)
let y: Option<i32> = None;
let nothing = y.map(|i| i * 2); // Returns NoneWe can chain operations without fear of exceptions:
fn name_length(name: Option<String>) -> Option<usize> {
name
.map(|s| s.trim().to_string()) // Remove spaces
.filter(|s| !s.is_empty()) // If empty, convert to None
.map(|s| s.len()) // Calculate length
}The ? operator with Option
Just like with Result, the ? operator works with Option.
If the value is Some, it extracts it. If it’s None, it returns None immediately from the current function.
fn first_element_doubled(list: Vec<i32>) -> Option<i32> {
// .first() returns Option<&i32>
let first = list.first()?;
// If the list was empty, the previous line returned None.
// If we got here, 'first' holds the value (unwrapped).
Some(first * 2)
}