ExamplesBy LevelBy TopicLearning Paths
305 Intermediate

305: unwrap_or, unwrap_or_else, unwrap_or_default

Functional Programming

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

  • • Understand the three variants: unwrap_or(val), unwrap_or_else(f), unwrap_or_default()
  • • Use unwrap_or_else for expensive default computations to avoid computing when not needed
  • • Use unwrap_or_default() for types implementing Default (0 for numbers, "" for strings)
  • • Recognize that unwrap_or(val) always evaluates val — use unwrap_or_else for lazy computation
  • Code 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

  • 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.
  • Default trait: unwrap_or_default() is unique to Rust — OCaml has no equivalent Default typeclass.
  • Result too: These methods exist on Result too: result.unwrap_or(0) extracts the Ok value or returns 0 on Err.
  • Panic avoidance: These methods are always safe; 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);
        }
    }
    ✓ Tests Rust test suite
    #[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

    PatternRustOCaml
    Eagerunwrap_or(x)Option.value ~default:x
    Lazyunwrap_or_else(f)Option.value_or_thunk
    Defaultunwrap_or_default()N/A

    Exercises

  • Implement a configuration reader that uses unwrap_or_else(|| read_default_config()) to lazily load defaults only when no user config is found.
  • Use unwrap_or_default() to provide empty defaults for a struct with multiple Option<Vec<String>> fields.
  • Compare opt.unwrap_or(expensive_computation()) vs opt.unwrap_or_else(|| expensive_computation()) — when does the difference matter?
  • Open Source Repos