Start learning free

LESSON 108 OF 414 · ARRAYS I

Option and Some in Rust: Handling Missing Values Without null

Lesson Read the full explanation

Most languages have a special value — null, nil, None — that means "nothing here". It is also the classic source of crashes: you forget to check, and the program dies. Rust takes a different route: there is no null. When a value might be absent, its type says so explicitly: Option<T>.

An Option<T> is always one of exactly two things:

  • Some(v) — there IS a value, and it's v
  • None — there is no value

You meet Options immediately when working with vectors. nums.first() and nums.get(i) can't promise an element exists (the vector might be empty, the index might be too big), so they return Option<&i32> instead of a plain number:

let nums = vec![10, 20, 30];
nums.get(1);   // Some(&20) — index 1 exists
nums.get(9);   // None — out of bounds, but NO crash

Three ways to get the value out:

// 1. if let — run code only when there IS a value (safe)
if let Some(v) = nums.first() {
    println!("got {}", v);
}

// 2. unwrap_or — take the value, or a fallback on None (safe)
let x = nums.get(9).unwrap_or(&-1);   // x is &-1

// 3. unwrap — take the value, PANIC on None (dangerous)
let y = nums.get(1).unwrap();          // fine: index 1 exists

The trap:

Calling .unwrap() on a None kills your program at runtime. This code compiles fine:

let nums = vec![10, 20, 30];
let tenth = nums.get(9).unwrap();   // get(9) is None...

...and then panics the moment it runs:

thread 'main' panicked at src/main.rs:3:29:
called `Option::unwrap()` on a `None` value
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Read that message carefully — you WILL see it again. The rule: use if let or unwrap_or by default, and save unwrap() for when you can prove the value exists.


Assignment
Follow the Coding Tutorial and practice all three: create let nums = vec![10, 20, 30];, then

  • take nums.first() and print it via if let Some(v),
  • take nums.get(9) and print it via .unwrap_or(&-1),
  • print nums.get(1).unwrap() — safe here, because index 1 certainly exists.

The exact expected output is:

first = 10
missing = -1
second = 20

Hint
get returns Option<&i32> — a reference — so the fallback in unwrap_or must be a reference too: &-1.


STEP 1 OF 7
    Ctrl + Enter to check

    RUST · ARRAYS I

    Option And Some in Rust, explained

    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 is v;
    • 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.

    1. Create the vector. nums holds 10, 20 and 30, so that some positions exist and some don't.
    2. Take the first element. Bind nums.first() to first. It is the option itself, Some(&10), with nothing unwrapped yet.
    3. Print it safely. if let Some(v) = first opens a block that runs only when a value exists, and inside it println!("first = {}", v) prints first = 10. A reference prints as the value it points to.
    4. Look up a missing position. Bind nums.get(9) to missing. It is None, and there's no crash.
    5. Print with a fallback. missing.unwrap_or(&-1) yields &-1 because the option is empty, so the line is missing = -1.
    6. Print an element that certainly exists. nums.get(1).unwrap() is safe because index 1 is the second element, so the line is second = 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>, either Some(v) or None.
    • first() and get(i) on a vector return Option<&T> instead of panicking.
    • Use if let or match to act on the value safely, and unwrap_or to supply a default.
    • unwrap() panics on None; use it only when the value is guaranteed.
    Keep going with RustAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free