Why Rust has no null
Many languages use a special value, such as null or None, for "nothing here". The trouble is that any variable might hold it, and forgetting to check leads to a crash. Rust has no such value in its ordinary types. When a value might be absent, the type says so with Option<T>, and the compiler won't let you use the value until you have dealt with the empty case.
An Option<T> is always exactly one of two things:
Some(v): there is a value, and it isv;None: there is no value.
An Option<i32> is not an i32. You can't add 1 to it, compare it with a number or print it with {}. You must first extract the number, and that is where the safety comes from.
Where you meet Options in vectors
Any operation that might not find an element returns an Option instead of failing. For a vector, first() and get(i) do this: the vector might be empty, or the index might be too large. Both return Option<&i32> for a vector of integers. The & means the option holds a reference to the element inside the vector, not a copy.
let nums = vec![10, 20, 30];
println!("{:?} {:?}", nums.get(1), nums.get(9));
Some(20) None
Compare this with indexing: nums[9] panics, while nums.get(9) is simply None and the program carries on. An empty vector gives None for first() too.
Three ways to get the value out
1. if let: run code only when there is a value
The pattern Some(v) matches only the Some case and gives the inner value the name v, which exists only inside the block. For None, the block is skipped.
if let Some(v) = nums.first() {
println!("got {}", v);
}
When you need to handle both cases, match covers them: match nums.get(2) { Some(v) => println!("has {}", v), None => println!("none") } prints has 30.
2. unwrap_or: take the value, or a fallback
This returns the inner value of a Some, or the argument you gave when the option is None. The fallback must have the same type as the contents, and here that is a reference, so it is written &-1, not -1:
let x = nums.get(9).unwrap_or(&-1);
If you'd rather have a plain number than a reference, nums.get(9).copied().unwrap_or(0) copies the value out of the reference first.
3. unwrap: take the value or panic
The method unwrap returns the inner value of a Some and panics on None. It compiles either way, which is exactly why it is risky:
let tenth = nums.get(9).unwrap();
This program builds, then stops at run time with called `Option::unwrap()` on a `None` value. Read that message carefully, because it is a common one. The rule of thumb is to use if let, match or unwrap_or by default and keep unwrap() for places where you can prove the value is there, as with nums.get(1) on a vector of three elements.
The lesson's exercise
The expected output is three lines, produced one step at a time.
- Create the vector.
numsholds 10, 20 and 30, so that some positions exist and some don't. - Take the first element. Bind
nums.first()tofirst. It is the option itself,Some(&10), with nothing unwrapped yet. - Print it safely.
if let Some(v) = firstopens a block that runs only when a value exists, and inside itprintln!("first = {}", v)printsfirst = 10. A reference prints as the value it points to. - Look up a missing position. Bind
nums.get(9)tomissing. It isNone, and there's no crash. - Print with a fallback.
missing.unwrap_or(&-1)yields&-1because the option is empty, so the line ismissing = -1. - Print an element that certainly exists.
nums.get(1).unwrap()is safe because index 1 is the second element, so the line issecond = 20.
let nums = vec![10, 20, 30];
let first = nums.first();
if let Some(v) = first {
println!("first = {}", v);
}
let missing = nums.get(9);
println!("missing = {}", missing.unwrap_or(&-1));
println!("second = {}", nums.get(1).unwrap());
first = 10
missing = -1
second = 20
Key takeaways
- Rust has no null: a value that may be absent has type
Option<T>, eitherSome(v)orNone. first()andget(i)on a vector returnOption<&T>instead of panicking.- Use
if letormatchto act on the value safely, andunwrap_orto supply a default. unwrap()panics onNone; use it only when the value is guaranteed.
