Nugget 15

Home

Option and Result as Iterators

Some(5).map(|x| x * 2) — wait, Option has map? Yes. Option and Result behave like iterators of 0 or 1 items, and they share many of the same combinators.

Option Is an Iterator (0 or 1 Items)

Option implements IntoIterator, so you can use it in a for loop:

let present: Option<i32> = Some(42);
let absent: Option<i32> = None;

for x in present {
    println!("{x}");  // prints 42
}

for x in absent {
    println!("{x}");  // nothing — None yields 0 items
}

// Practical: iterate over only the present values
let items = [Some(1), None, Some(2), None, Some(3)];
let sum: i32 = items.iter()
    .flatten()     // drops None, unwraps Some
    .sum();
assert_eq!(sum, 6);

Option::map() and Friends

Like iterators, Option has combinators that only run when a value is present:

let x: Option<i32> = Some(5);
let y: Option<i32> = None;

// map — transform the inner value if present
assert_eq!(x.map(|n| n * 2), Some(10));
assert_eq!(y.map(|n| n * 2), None);  // no-op

// and_then — map to another Option (flat_map equivalent)
assert_eq!(
    x.and_then(|n| if n > 3 { Some(n * 2) } else { None }),
    Some(10)
);
assert_eq!(
    y.and_then(|n| if n > 3 { Some(n * 2) } else { None }),
    None
);

// unwrap_or — fallback value
assert_eq!(x.unwrap_or(0), 5);
assert_eq!(y.unwrap_or(0), 0);

// Chaining: parse a string, then process
let result: Option<i32> = "42"
    .parse::<i32>()       // Result — use .ok() to convert to Option
    .ok()
    .map(|n| n * 2)
    .filter(|n| n > 50);   // Some(84) — 84 > 50, keeps it
assert_eq!(result, Some(84));

💡 map vs and_then

map wraps the result back in Some: map(|x| x + 1)Some(6). and_then expects the closure to return an Option: and_then(|x| if x > 0 { Some(x) } else { None }).

Result::map() and Friends

Result works the same way, with variants for the error case:

let ok: Result<i32, String> = Ok(42);
let err: Result<i32, String> = Err("failed".into());

// map — transform Ok value only
assert_eq!(ok.map(|n| n * 2), Ok(84));
assert_eq!(err.map(|n| n * 2), Err("failed".into()));

// map_err — transform Err value only
assert_eq!(ok.map_err(|e| format!("error: {e}")), Ok(42));
assert_eq!(
    err.map_err(|e| format!("error: {e}")),
    Err("error: failed".into())
);

// and_then — chain fallible operations
fn double_if_even(n: i32) -> Result<i32, String> {
    if n % 2 == 0 { Ok(n * 2) }
    else { Err(format!("{n} is odd")) }
}

assert_eq!(ok.and_then(double_if_even), Ok(84));
assert_eq!(err.and_then(double_if_even), Err("failed".into()));

// ok() / err() — convert to Option
assert_eq!(ok.ok(), Some(42));
assert_eq!(err.ok(), None);

The Great collect Idiom

One of Rust's most elegant patterns: collect can gather an iterator of Results into a single Result<Vec<T>, E>:

// Parse a list of strings into numbers
let inputs = ["10", "20", "30", "40"];

let parsed: Result<Vec<i32>, _> = inputs.iter()
    .map(|s| s.parse::<i32>())
    .collect();

assert_eq!(parsed, Ok(vec![10, 20, 30, 40]));

// With an invalid input — first error stops everything
let inputs = ["10", "twenty", "30"];

let parsed: Result<Vec<i32>, _> = inputs.iter()
    .map(|s| s.parse::<i32>())
    .collect();

assert!(parsed.is_err());  // Err(ParseIntError)
// No partial Vec — either all succeed or the first error is returned.

📐 collect into Result

This works because Result implements FromIterator. If any item is Err, the first error is returned and no Vec is allocated. Same pattern works with Option: collect::<Option<Vec<_>>>() (first None stops the collection).

A Real-World Pipeline

Combining everything — iterator adapters, Option combinators, and collect into Result:

use std::collections::HashMap;

let csv_data = "alice,30\nbob,twenty-five\ncharlie,35";

// Parse lines into a HashMap<String, i32> — fail on any error
let ages: Result<HashMap<&str, i32>, _> = csv_data
    .lines()
    .filter(|line| !line.is_empty())
    .map(|line| {
        // Split "alice,30" → (&str, Result)
        let parts: Vec<&str> = line.split(',').collect();
        let name = parts[0];
        let age = parts[1].parse::<i32>();
        age.map(|a| (name, a))  // Ok → Ok((name, age))
    })
    .collect();  // Stops at first error

// Without the bad line, this would work perfectly.
// 'bob,twenty-five' causes a parse error — no partial data.

This is the iterator model at its best: describe a pipeline, handle errors at the end, and never worry about partial failure.

Key Takeaways