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

MethodConvertsUse 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

MethodConvertsEffect
.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

MethodSignatureUse 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> TProvide a fallback
.map_or(default, f)Option<T> UMap + unwrap_or combined
.is_none_or(f)Option<T> boolTrue 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())  // None

If you used .map where you need .and_then, you’d get Option<Option<T>> — a nested Option.

Chaining within Result

MethodSignatureUse 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 ? unwraps

See also