🎓 A1Learner
🔍

Rust Option Defaults

Option default tools supply a fallback value when an Option is None. Some tools take a ready-made fallback, called eager, and some build it only when needed, called lazy.

or: the first Some wins

or keeps your Option when it has a value, and takes the next one when it does not. Calls chain, so you can line up several tries:

let choice: Option<&str> = None.or(Some("bus")).or(Some("train"));
println!("{choice:?}");

or builds its fallback either way

or takes a ready-made value. So the fallback is built before or even looks. That is called eager. Watch the baking line print, even though the pie wins:

fn bake() -> Option<String> {
    println!("baking a fallback");
    Some(String::from("cake"))
}
let ready = Some(String::from("pie"));
let meal = ready.or(bake());
println!("meal = {:?}", meal);

or_else builds only when needed

or_else takes a closure instead, and runs it only for a None. Same setup as above — this time the baking line never prints:

let ready = Some(String::from("pie"));
let meal = ready.or_else(|| {
    println!("baking a fallback");
    Some(String::from("cake"))
});
println!("meal = {:?}", meal);

get_or_insert fills the Option itself

The variable must be mut. The fallback is stored inside the Option, and the method hands back a look at what is stored:

let mut picked: Option<String> = None;
let value = picked.get_or_insert(String::from("lemon"));
println!("value = {value}");
println!("stored = {picked:?}");

get_or_insert_with fills lazily

This is the lazy twin of get_or_insert. The closure runs only when the Option is None. This Option already holds an apple, so no fallback is made:

let mut ready = Some(String::from("apple"));
let kept = ready.get_or_insert_with(|| {
    println!("making a fallback");
    String::from("lemon")
});
println!("kept = {kept}");

Example

fn main() {
    let none: Option<&str> = None;
    let first = none.or(Some("orange")).or(Some("apple"));
    println!("first = {:?}", first);
    let mut picked: Option<String> = None;
    let value = picked.get_or_insert_with(|| {
        println!("making a fallback");
        String::from("lemon")
    });
    println!("picked = {}", value);
    println!("stored = {:?}", picked);
    let mut ready = Some(String::from("apple"));
    let kept = ready.get_or_insert_with(|| String::from("lemon"));
    println!("kept = {}", kept);
}

Key points

  • When there is no value, a default tool steps in with a stand-in.
  • get_or_insert leaves the value stored in the Option.
  • unwrap_or takes the value out and leaves the Option behind.

In this example, none is empty. So the or chain takes the first fallback that has a value: Some("orange"). The apple is never used. Next, picked starts as None. get_or_insert_with runs its closure. The closure prints making a fallback and builds the text lemon. The value is stored inside picked. The method hands back a look at it. So picked = lemon prints, and stored shows Some("lemon"). Last, ready already holds an apple. get_or_insert_with leaves it alone — the closure never runs — and kept = apple prints.

Note

Pick eager for a cheap, ready value.

Pick lazy when making the fallback costs work, or when it must not happen unless it is truly needed.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Rust Playground

Exercise

Which method fills a None in place, and only builds the fallback when it is needed?