Rust Option and Result Conversions
Option and Result are both “container” enums that may or may not hold a value. Rust provides a rich set of methods to convert between them and chain operations. Understanding these is essential because real code constantly crosses the Option/Result boundary.
The core types
enum Option<T> { Some(T), None }
enum Result<T, E> { Ok(T), Err(E) }Converting between Option and Result
Option to Result
| Method | Converts | Use when |
|---|---|---|
.ok_or(err) | Option<T> → Result<T, E> | You have a concrete error value |
.ok_or_else(|| err) | Option<T> → Result<T, E> | Error is expensive to construct |
.context(SelectorSnafu { .. }) | Option<T> → Result<T, E> | Using snafu’s OptionExt |
let user: Option<User> = users.get(&id).cloned();
// Manual
let user: Result<User, StatusCode> = user.ok_or(StatusCode::NOT_FOUND);
// With snafu OptionExt
let user: Result<User, AppError> = user.context(NotFoundSnafu { id: &id });Result to Option
| Method | Converts | Effect |
|---|---|---|
.ok() | Result<T, E> → Option<T> | Discards the error, keeps Ok |
.err() | Result<T, E> → Option<E> | Discards success, keeps Err |
// HeaderValue::to_str() returns Result<&str, ToStrError>
// .ok() discards the error, giving Option<&str>
let value: Option<&str> = header_value.to_str().ok();This is commonly needed when chaining with Option methods like and_then:
// .get() returns Option<&HeaderValue>
// .to_str() returns Result<&str, ToStrError>
// .ok() bridges Result back into the Option chain
// .strip_prefix() returns Option<&str>
let token = req.headers()
.get(AUTHORIZATION) // Option<&HeaderValue>
.and_then(|v| v.to_str().ok()) // Option<&str> <-- .ok() is the bridge
.and_then(|s| s.strip_prefix("Bearer ")); // Option<&str>Without .ok(), this chain breaks — you can’t use and_then to go from Option through a Result and back to Option. The types don’t line up.
Chaining within Option
| Method | Signature | Use when |
|---|---|---|
.map(f) | Option<T> → Option<U> | Transform the inner value (infallible) |
.and_then(f) | Option<T> → Option<U> where f: T -> Option<U> | Transform can fail (returns Option) |
.filter(f) | Option<T> → Option<T> | Keep only if predicate holds |
.unwrap_or(default) | Option<T> → T | Provide a fallback |
.map_or(default, f) | Option<T> → U | Map + unwrap_or combined |
.is_none_or(f) | Option<T> → bool | True if None or predicate holds |
The key distinction is .map vs .and_then:
// .map — the closure returns a plain value, gets wrapped in Some
Some("42").map(|s| s.len()) // Some(2)
// .and_then — the closure returns Option, no double wrapping
Some("42").and_then(|s| s.parse::<i32>().ok()) // Some(42)
Some("xx").and_then(|s| s.parse::<i32>().ok()) // NoneIf you used .map where you need .and_then, you’d get Option<Option<T>> — a nested Option.
Chaining within Result
| Method | Signature | Use when |
|---|---|---|
.map(f) | Result<T, E> → Result<U, E> | Transform success value |
.map_err(f) | Result<T, E> → Result<T, F> | Transform error type |
.and_then(f) | Result<T, E> → Result<U, E> where f: T -> Result<U, E> | Chain fallible operations |
.context(Snafu) | Result<T, E> → Result<T, AppError> | snafu: wrap error with context |
// .map_err to convert error types for ?
let id: Uuid = input.parse()
.map_err(|_| StatusCode::BAD_REQUEST)?;
// .context to wrap with snafu
let rows = db.query(sql).await
.context(DatabaseSnafu { table: "users" })?;The ? operator
? is sugar for “if Err/None, return early”. It works on both Result and Option, but only if the function’s return type matches:
// In a function returning Result<T, E>:
let val = something()?; // returns Err(e) if Err, unwraps if Ok
// In a function returning Option<T>:
let val = something()?; // returns None if None, unwraps if Some? also applies From conversion on errors, so ? on Result<T, IoError> in a function returning Result<T, AppError> works if impl From<IoError> for AppError exists.
Common patterns
Flatten nested Options
let x: Option<Option<i32>> = Some(Some(42));
x.flatten() // Some(42)
// This is why and_then exists — it's map + flatten
some_val.map(|v| might_return_none(v)).flatten()
// equivalent to:
some_val.and_then(|v| might_return_none(v))Transpose Option and Result
let x: Option<Result<i32, E>> = Some(Ok(42));
x.transpose() // Result<Option<i32>, E> = Ok(Some(42))
let y: Result<Option<i32>, E> = Ok(Some(42));
y.transpose() // Option<Result<i32, E>> = Some(Ok(42))Useful when you have an optional field that might fail to parse:
// query param is Option<String>, parsing returns Result
let limit: Option<u32> = params.limit
.map(|s| s.parse::<u32>()) // Option<Result<u32, ParseIntError>>
.transpose()?; // Result<Option<u32>, ParseIntError>, then ? unwrapsSee also
- Rust patterns — newtype and other common Rust patterns
- Rust Closures — closure syntax used in map/and_then/filter