đź”” Prelude: When writing Rust programs, Result and Option are data structures we can hardly avoid. I believe that rather than understanding their underlying mechanisms, we should first quickly figure out how to handle them.

Gift Wrapping

Actually, we don’t need to think of Option<T> and Result<T,E> as too complicated:

  • Option contains the data we want, where the data type is T. However, there might be no data inside (None). But regardless, as long as what we care about is wrapped, it will be an Option.
  • Result also contains the data we want, but it comes with another “restless” additional item E (Error). In fact, combinations of Result and Option are very common, such as Result<Option<T>, E>. Result provides a powerful and unified specification for Rust’s error handling.

So, how do we open the gift box…

The moment of unwrapping gifts is always exciting.

Whether it’s Option or Result, we can use unwrap() to open this package.

  • If the gift box contains a normal value, everything goes smoothly, and everyone’s happy.
  • But if it contains something “weird” (None or Err), the program will immediately panic and crash.

Of course, if we insist on using safer unwrapping methods (like unwrap_or, match, etc.), we can control whether the program crashes ourselves, which is very practical.

What functions do these gift boxes provide?

Let’s quickly go through the methods they provide. Detailed explanations won’t be provided here, as I believe you can look up relevant information through https://doc.rust-lang.org/std/option/ and https://doc.rust-lang.org/std/result/.

Option

APIPurposeReturn Value and Notes
is_some()Check if it’s Somebool
is_none()Check if it’s Nonebool
unwrap()Get the value inside Some, panic if NoneT / panic
unwrap_or(default)Get value, return specified default if NoneT
unwrap_or_else(f)Get value, call closure f to return default if NoneT
map(f)If Some, call f on inner value, return new OptionOption<U>
and_then(f)If Some, call f, return another OptionOption<U>
or(other)If None, return parameter other, otherwise return selfOption<T>
or_else(f)If None, call closure f to return an OptionOption<T>
as_ref()Convert Option<T> to immutable reference of inner value Option<&T>Option<&T>
as_mut()Convert Option<T> to mutable reference of inner value Option<&mut T>Option<&mut T>
ok_or(err)Convert Some(value) to Ok(value), and None to Err(err)Result<T, E>
ok_or_else(f)Same as above, but call closure if NoneResult<T, E>
expect(msg)Similar to unwrap(), but allows custom error messageT / panic
?Return None directly when None (must be used in functions returning Option)T / early return

Notable methods:

  • map(f) provides a way to operate on inner data without unpacking, very commonly used
  • expect("Hi") can display error messages when panicking, more practical than unwrap()
  • ? can greatly improve code cleanliness
  • is_some() and is_none() are also very useful for making decisions

Result

Compared to the intuitive and easy-to-understand Option, Result seems more “convoluted” with an additional type, but essentially it’s almost identical to Option: what to do if we didn’t get the value (possibly due to an error).

APIPurposeReturn Value / Notes
is_ok()Check if it’s Okbool
is_err()Check if it’s Errbool
unwrap()Get value inside Ok, panic if ErrT / panic
unwrap_or(default)Get value, return default if ErrT
unwrap_or_else(f)Get value, call closure f to return default if ErrT
map(f)If Ok, call f on inner value, return new ResultResult<U, E>
and_then(f)If Ok, call f, return another ResultResult<U, E>
map_err(f)If Err, call f on error, return new ResultResult<T, F>
or(other)If Err, return other, otherwise return selfResult<T, E>
or_else(f)If Err, call closure f to return ResultResult<T, E>
as_ref()Convert to immutable referencesResult<&T, &E>
as_mut()Convert to mutable referencesResult<&mut T, &mut E>
ok()Success value → Some(value), error → NoneOption<T>
err()Error value → Some(error), success → NoneOption<E>
expect(msg)Similar to unwrap(), but allows custom error messageT / panic
?Return early when encountering Err (must be used in functions returning Result)T / early return

Result also has: map(f) and ?, as well as useful is_ok() and is_err()

In Result<T, E>:

  • T is the type of value returned on success (e.g., file content, calculation result, etc.).
  • E is the type of value returned on error (e.g., std::io::Error, custom error enum, etc.).
  • Actually:
    • E can be any type, with no restrictions
    • But to use ?, facilitate debugging and printing, E usually needs to implement Debug, Display, Error, and other traits
    • Yes, E can be very complex types (carrying context, providing helper methods), or very simple error structures (like, a string). Err is just the shell that Result uses to mark “this is the error branch”, with E inside

Examples

// ===== Creating Option =====
let some_value: Option<i32> = Some(42);  // Has value
let none_value: Option<i32> = None;      // No value

// Common usage: returning from functions
fn find_number(flag: bool) -> Option<i32> {
    if flag { Some(7) } else { None }
}

// ===== Creating Result =====
let ok_value: Result<i32, String> = Ok(42);                    // Success
let err_value: Result<i32, String> = Err("Oops".to_string()); // Failure

// Common usage: returning from functions
fn divide(a: i32, b: i32) -> Result<i32, String> {
    if b == 0 {
        Err("Division by zero".to_string())
    } else {
        Ok(a / b)
    }
}