Option and Result Combinators
Transform an Option value
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
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
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
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
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(())
}