Nugget 15
HomeOption 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);
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).
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.
Option and Result behave like iterators of 0 or 1 items — map, and_then, and for loops all work.map transforms the inner value; and_then chains fallible operations (flat_map for Option/Result).collect::<Result<Vec<T>, E>>() from an iterator of Results — stops at the first error.flatten() on an iterator of Options drops all Nones.