Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Option and Result Combinators

Transform an Option value

std-badge cat-rust-patterns-badge

Rather than matching on every Option by hand, the standard library provides combinators that transform the contained value in place. This recipe classifies an uploaded file by extension: Option::and_then chains Path::extension into OsStr::to_str, where each step may itself yield None; Option::map normalizes the matched value; and Option::filter rejects anything that is not a supported image. None falls straight through the chain, so no intermediate value needs to be unwrapped.

use std::path::Path;

/// Return the lowercase extension of `filename`, but only when it names an
/// image we are willing to accept.
fn image_extension(filename: &str) -> Option<String> {
    Path::new(filename)
        .extension() // `Option<&OsStr>`: `None` when there is no extension
        .and_then(|ext| ext.to_str()) // `and_then` chains another `Option`-returning step
        .map(|ext| ext.to_lowercase()) // `map` normalizes the matched value
        .filter(|ext| matches!(ext.as_str(), "png" | "jpg" | "jpeg" | "gif"))
}

fn main() {
    println!("photo.JPG      -> {:?}", image_extension("photo.JPG"));
    println!("archive.tar.gz -> {:?}", image_extension("archive.tar.gz"));
    println!("README         -> {:?}", image_extension("README"));

    assert_eq!(image_extension("photo.JPG"), Some("jpg".to_string()));
    assert_eq!(image_extension("archive.tar.gz"), None);
    assert_eq!(image_extension("README"), None);
}

Provide fallbacks for missing values

std-badge cat-rust-patterns-badge

When a value may be absent, the right way to supply a default depends on how expensive that default is to produce. Option::unwrap_or takes a value that is always evaluated, so it suits cheap constants. Option::unwrap_or_else defers to a closure, computing the fallback only when the value is None — prefer it when the default is costly or has side effects. Option::unwrap_or_default returns the type’s Default value.

fn expensive_default() -> i32 {
    // Pretend this performs costly work we want to avoid when possible.
    42
}

fn main() {
    let present: Option<i32> = Some(7);
    let missing: Option<i32> = None;

    // `unwrap_or` returns the value, or an eagerly evaluated default.
    println!("present.unwrap_or(0)       = {}", present.unwrap_or(0));
    println!("missing.unwrap_or(0)       = {}", missing.unwrap_or(0));
    assert_eq!(present.unwrap_or(0), 7);
    assert_eq!(missing.unwrap_or(0), 0);

    // `unwrap_or_else` defers the default to a closure, so the
    // fallback runs only when the value is absent.
    println!("missing.unwrap_or_else(..) = {}", missing.unwrap_or_else(expensive_default));
    assert_eq!(missing.unwrap_or_else(expensive_default), 42);

    // `unwrap_or_default` uses the type's `Default` implementation.
    println!("missing.unwrap_or_default  = {}", missing.unwrap_or_default());
    assert_eq!(missing.unwrap_or_default(), 0);
}

Convert an Option to a Result

std-badge cat-rust-patterns-badge

A missing Option often needs to become a meaningful error so it can be propagated with ?. Option::ok_or turns Some(v) into Ok(v) and None into Err(e) using a supplied error value. Option::ok_or_else builds that error lazily from a closure, which is preferable when constructing the error allocates or is otherwise expensive.

use std::collections::HashMap;

#[derive(Debug, PartialEq)]
struct MissingKey(String);

fn get_setting(config: &HashMap<String, String>, key: &str) -> Result<String, MissingKey> {
    // `ok_or_else` bridges `Option` to `Result`: `Some(v)` becomes
    // `Ok(v)`, and `None` becomes `Err`, with the error built lazily.
    config
        .get(key)
        .cloned()
        .ok_or_else(|| MissingKey(key.to_string()))
}

fn main() -> Result<(), MissingKey> {
    let mut config = HashMap::new();
    config.insert("host".to_string(), "localhost".to_string());

    let host = get_setting(&config, "host")?;
    println!("host setting: {host}");
    assert_eq!(host, "localhost");

    // Use `ok_or` when the error value is cheap to create up front.
    let port: Result<&String, MissingKey> =
        config.get("port").ok_or(MissingKey("port".to_string()));
    println!("port setting: {port:?}");
    assert_eq!(port, Err(MissingKey("port".to_string())));

    Ok(())
}

Chain fallible operations with Result

std-badge cat-rust-patterns-badge

Result carries the same family of combinators as Option, letting a sequence of fallible steps read as a single expression. Result::map_err rewrites an error into a domain-specific type so it can cross an API boundary. Result::and_then sequences another fallible step, short-circuiting at the first error. Result::map transforms a successful value while leaving any error untouched.

#[derive(Debug)]
enum ConfigError {
    NotANumber(String),
    OutOfRange(u32),
}

fn socket_addr(host: &str, raw_port: &str) -> Result<String, ConfigError> {
    raw_port
        .parse::<u32>()
        // `map_err` rewrites the standard-library error as our own type.
        .map_err(|_| ConfigError::NotANumber(raw_port.to_string()))
        // `and_then` runs another fallible step, short-circuiting on error.
        .and_then(|n| u16::try_from(n).map_err(|_| ConfigError::OutOfRange(n)))
        // `map` transforms the success value, leaving any error untouched.
        .map(|port| format!("{host}:{port}"))
}

fn main() -> Result<(), ConfigError> {
    println!("valid:      {:?}", socket_addr("localhost", "8080"));
    println!("not number: {:?}", socket_addr("localhost", "nope"));
    println!("out of range: {:?}", socket_addr("localhost", "70000"));

    assert_eq!(socket_addr("localhost", "8080")?, "localhost:8080");
    assert!(matches!(
        socket_addr("localhost", "nope"),
        Err(ConfigError::NotANumber(_))
    ));
    assert!(matches!(
        socket_addr("localhost", "70000"),
        Err(ConfigError::OutOfRange(_))
    ));

    Ok(())
}

Collect an iterator of Results

std-badge cat-rust-patterns-badge

When every item of an iterator is fallible, Iterator::collect can gather them into a single Result<Vec<_>, _>. It returns the whole vector only if every element is Ok, short-circuiting to the first Err otherwise. When failures should instead be skipped, Iterator::filter_map paired with Result::ok keeps only the successful values.

Iterator::collect needs to know which collection to build. The two calls below show the interchangeable ways to tell it: a turbofish on collect (collect::<Result<Vec<_>, _>>()) or a type annotation on the binding (let result: Result<Vec<i32>, _>). Pick whichever reads better at the call site; the result is identical.

fn main() -> Result<(), std::num::ParseIntError> {
    let raw = ["1", "2", "3"];

    // Collecting into `Result<Vec<_>, _>` yields the whole vector only
    // when every item parses successfully.
    let numbers: Vec<i32> = raw
        .iter()
        .map(|s| s.parse::<i32>())
        .collect::<Result<Vec<_>, _>>()?;
    println!("all parsed:    {numbers:?}");
    assert_eq!(numbers, vec![1, 2, 3]);

    // A single bad value short-circuits the entire collection into `Err`.
    let mixed = ["1", "x", "3"];
    let result: Result<Vec<i32>, _> = mixed.iter().map(|s| s.parse::<i32>()).collect();
    println!("short-circuit: {result:?}");
    assert!(result.is_err());

    // To ignore failures instead, use `filter_map` to keep the `Ok` values.
    let valid: Vec<i32> = mixed.iter().filter_map(|s| s.parse::<i32>().ok()).collect();
    println!("skip failures: {valid:?}");
    assert_eq!(valid, vec![1, 3]);

    Ok(())
}