Option and Result Idioms
Rule
Use simple combinators for local transformations and explicit branching when absence or failure carries behavior, side effects, context, or recovery.
Why
Short combinators keep linear transformations compact, while behavior-bearing decisions need visible control flow.
Do
- Use
?to propagate within the sameResultorOptionshape. - Convert required optional values with
ok_orfor cheap errors andok_or_elsefor computed errors. - Use
map,filter, andunwrap_or_elsefor short side-effect-free transforms. - Use
map_errfor typed conversion that retains the source. - Use
transposeto turnOption<Result<T, E>>intoResult<Option<T>, E>. - Use explicit branching for logging, metrics, cleanup, retries, or recovery.
- Add context according to error propagation.
Avoid
- Do not chain combinators past easy readability.
- Do not hide side effects in
map,and_then,or_else, orinspect. - Do not call
.ok()unless detailed failure intentionally becomes absence. - Do not eagerly evaluate an expensive fallback with
unwrap_or. - Do not use
unwrap_or_defaultwhen absence is a domain error. - Do not check
is_someand then unwrap.
Example
#![allow(unused)]
fn main() {
pub fn build_request(input: &Input) -> Result<Request, Error> {
let id = input.id().ok_or(Error::MissingId)?;
let mode = input
.mode()
.map(Mode::parse)
.transpose()?
.unwrap_or_else(Mode::default);
let timeout = match input.timeout_ms() {
Some(0) => return Err(Error::InvalidTimeout),
Some(ms) => Timeout::from_millis(ms)?,
None => Timeout::default(),
};
Ok(Request::new(id, mode, timeout))
}
}
Exceptions
- Use a longer chain when every step is pure and clearly named.
- Use
matchfor a simple case when exhaustiveness documents the domain. - Use
.ok()at an external boundary where absence is the deliberate projection.