305: unwrap_or, unwrap_or_else, unwrap_or_default
Tutorial Video
Text description (accessibility)
This video demonstrates the "305: unwrap_or, unwrap_or_else, unwrap_or_default" functional Rust example. Difficulty level: Intermediate. Key concepts covered: Functional Programming. Extracting a value from `Option<T>` or `Result<T, E>` when a sensible default exists is extremely common: getting a config value or using a default, parsing a number or falling back to 0, looking up a cache entry or computing a fresh value. Key difference from OCaml: 1. **Eagerness distinction**: `unwrap_or(val)` is eager (OCaml's `Option.value` is also eager); `unwrap_or_else(f)` is lazy — an important performance distinction for expensive defaults.
Tutorial
The Problem
Extracting a value from Option<T> or Result<T, E> when a sensible default exists is extremely common: getting a config value or using a default, parsing a number or falling back to 0, looking up a cache entry or computing a fresh value. The unwrap_or family provides safe alternatives to unwrap() that handle the None/Err case without panicking. The three variants differ in when the default value is computed — eagerly, lazily, or from the type's Default implementation.
🎯 Learning Outcomes
unwrap_or(val), unwrap_or_else(f), unwrap_or_default()unwrap_or_else for expensive default computations to avoid computing when not neededunwrap_or_default() for types implementing Default (0 for numbers, "" for strings)unwrap_or(val) always evaluates val — use unwrap_or_else for lazy computationCode Example
#![allow(clippy::all)]
//! # unwrap_or, unwrap_or_else, unwrap_or_default
//!
//! Safe alternatives to `unwrap()` when a default value is available.
/// Get value with eager default
pub fn get_or(opt: Option<i32>, default: i32) -> i32 {
opt.unwrap_or(default)
}
/// Get value with lazy default
pub fn get_or_compute<F: FnOnce() -> i32>(opt: Option<i32>, f: F) -> i32 {
opt.unwrap_or_else(f)
}
/// Get value using type's Default
pub fn get_or_default<T: Default>(opt: Option<T>) -> T {
opt.unwrap_or_default()
}
/// Parse with default on failure
pub fn parse_or_default(s: &str, default: i32) -> i32 {
s.parse::<i32>().unwrap_or(default)
}
/// Config-style loading with default
pub fn load_port(env_val: Option<String>) -> u16 {
env_val.and_then(|s| s.parse().ok()).unwrap_or(8080)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_unwrap_or_some() {
assert_eq!(get_or(Some(5), 0), 5);
}
#[test]
fn test_unwrap_or_none() {
assert_eq!(get_or(None, 0), 0);
}
#[test]
fn test_unwrap_or_else_not_called() {
let mut called = false;
let _ = get_or_compute(Some(5), || {
called = true;
0
});
assert!(!called);
}
#[test]
fn test_unwrap_or_else_called() {
let mut called = false;
let val = get_or_compute(None, || {
called = true;
42
});
assert!(called);
assert_eq!(val, 42);
}
#[test]
fn test_unwrap_or_default_vec() {
let v: Vec<i32> = get_or_default(None);
assert!(v.is_empty());
}
#[test]
fn test_unwrap_or_default_string() {
let s: String = get_or_default(None);
assert_eq!(s, "");
}
#[test]
fn test_parse_or_default() {
assert_eq!(parse_or_default("42", 0), 42);
assert_eq!(parse_or_default("bad", 0), 0);
}
#[test]
fn test_load_port() {
assert_eq!(load_port(Some("3000".to_string())), 3000);
assert_eq!(load_port(None), 8080);
}
}Key Differences
unwrap_or(val) is eager (OCaml's Option.value is also eager); unwrap_or_else(f) is lazy — an important performance distinction for expensive defaults.unwrap_or_default() is unique to Rust — OCaml has no equivalent Default typeclass.Result too: result.unwrap_or(0) extracts the Ok value or returns 0 on Err.unwrap() panics on None/Err — reserve unwrap() for provably non-None situations or tests.OCaml Approach
OCaml uses Option.value opt ~default:val (Base library) or Option.fold ~none:default ~some:Fun.id opt:
(* Eager default *)
let get_or opt default = Option.value opt ~default
(* Lazy default (Base library) *)
let get_or_lazy opt f = Option.value_or_thunk opt ~f
(* Default via Option.fold *)
let get_or_zero opt = Option.fold ~none:0 ~some:Fun.id opt
Full Source
#![allow(clippy::all)]
//! # unwrap_or, unwrap_or_else, unwrap_or_default
//!
//! Safe alternatives to `unwrap()` when a default value is available.
/// Get value with eager default
pub fn get_or(opt: Option<i32>, default: i32) -> i32 {
opt.unwrap_or(default)
}
/// Get value with lazy default
pub fn get_or_compute<F: FnOnce() -> i32>(opt: Option<i32>, f: F) -> i32 {
opt.unwrap_or_else(f)
}
/// Get value using type's Default
pub fn get_or_default<T: Default>(opt: Option<T>) -> T {
opt.unwrap_or_default()
}
/// Parse with default on failure
pub fn parse_or_default(s: &str, default: i32) -> i32 {
s.parse::<i32>().unwrap_or(default)
}
/// Config-style loading with default
pub fn load_port(env_val: Option<String>) -> u16 {
env_val.and_then(|s| s.parse().ok()).unwrap_or(8080)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_unwrap_or_some() {
assert_eq!(get_or(Some(5), 0), 5);
}
#[test]
fn test_unwrap_or_none() {
assert_eq!(get_or(None, 0), 0);
}
#[test]
fn test_unwrap_or_else_not_called() {
let mut called = false;
let _ = get_or_compute(Some(5), || {
called = true;
0
});
assert!(!called);
}
#[test]
fn test_unwrap_or_else_called() {
let mut called = false;
let val = get_or_compute(None, || {
called = true;
42
});
assert!(called);
assert_eq!(val, 42);
}
#[test]
fn test_unwrap_or_default_vec() {
let v: Vec<i32> = get_or_default(None);
assert!(v.is_empty());
}
#[test]
fn test_unwrap_or_default_string() {
let s: String = get_or_default(None);
assert_eq!(s, "");
}
#[test]
fn test_parse_or_default() {
assert_eq!(parse_or_default("42", 0), 42);
assert_eq!(parse_or_default("bad", 0), 0);
}
#[test]
fn test_load_port() {
assert_eq!(load_port(Some("3000".to_string())), 3000);
assert_eq!(load_port(None), 8080);
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_unwrap_or_some() {
assert_eq!(get_or(Some(5), 0), 5);
}
#[test]
fn test_unwrap_or_none() {
assert_eq!(get_or(None, 0), 0);
}
#[test]
fn test_unwrap_or_else_not_called() {
let mut called = false;
let _ = get_or_compute(Some(5), || {
called = true;
0
});
assert!(!called);
}
#[test]
fn test_unwrap_or_else_called() {
let mut called = false;
let val = get_or_compute(None, || {
called = true;
42
});
assert!(called);
assert_eq!(val, 42);
}
#[test]
fn test_unwrap_or_default_vec() {
let v: Vec<i32> = get_or_default(None);
assert!(v.is_empty());
}
#[test]
fn test_unwrap_or_default_string() {
let s: String = get_or_default(None);
assert_eq!(s, "");
}
#[test]
fn test_parse_or_default() {
assert_eq!(parse_or_default("42", 0), 42);
assert_eq!(parse_or_default("bad", 0), 0);
}
#[test]
fn test_load_port() {
assert_eq!(load_port(Some("3000".to_string())), 3000);
assert_eq!(load_port(None), 8080);
}
}
Deep Comparison
unwrap_or Patterns
| Pattern | Rust | OCaml |
|---|---|---|
| Eager | unwrap_or(x) | Option.value ~default:x |
| Lazy | unwrap_or_else(f) | Option.value_or_thunk |
| Default | unwrap_or_default() | N/A |
Exercises
unwrap_or_else(|| read_default_config()) to lazily load defaults only when no user config is found.unwrap_or_default() to provide empty defaults for a struct with multiple Option<Vec<String>> fields.opt.unwrap_or(expensive_computation()) vs opt.unwrap_or_else(|| expensive_computation()) — when does the difference matter?