Here are some general guidelines to keep in mind.

Functions that return Result in std

Many of the std functions return Results if the action you’re trying to perform can fail. Here’s an example from the Write trait in std::io:

trait Write {
  fn write_all(&mut self, buf: &[u8]) -> Result<()>
  ...
}

Using the write_all method to write the contents of the supplied buffer (buf) can fail with IO errors, or it could succeed with a Unit (()).

Result should have two type variables and the example above clearly only has one. What’s going on?

A frequent pattern used in Rust libraries is to create a type alias for Result that wraps a particular error type. In the example above, Result is aliased as follows:

pub type Result<T> = Result<T, Error>;

Where Error is a std::io::Error:

pub struct Error { /* private fields */ }

Essentially giving you:

pub type Result<T> = Result<T, std::io::Error>;

With a type alias like above, we don’t have to constantly specify a type for a Result’s error. This is useful where many methods return the same error type. For example, all std::io methods that return Result use std::io::Error as the error type.

Eager vs Laziness

There’s a distinction that applies to all variants for Result methods that take in a value vs a function. Let’s take unwrap_or and unwrap_or_else as an example. As a refresher, here are the definitions for both functions.

unwrap_or:

pub fn unwrap_or(self, default: T) -> T {
    match self {
        Ok(t) => t,
        Err(_) => default,
    }
}

In the above, default is a value.

unwrap_or_else:

pub fn unwrap_or_else<F: FnOnce(E) -> T>(self, op: F) -> T {
    match self {
        Ok(t) => t,
        Err(e) => op(e),
    }
}

In the above, op is a function.

With unwrap_or_else, the function supplied (op) will not get called unless there is an Err value to call it with; it will not get called on Ok values. This is different to unwrap_or’s default value which is evaluated on Ok values as well:

  let strict_result_ok: Result<u32, String> = Ok(1);
  let strict_result_err: Result<u32, String> = Err("You have errors".to_owned());

  strict_result_ok.unwrap_or(panic!("boom")); // panics even though this is an `Ok`
  strict_result_ok.unwrap_or_else(|_| panic!("boom")); // does not panic because this is an `Ok`
  strict_result_err.unwrap_or_else(|_| panic!("boom")); // panics on `Err` as expected

You can think of unwrap_or as being “eager” in its evaluation of the default parameter - it always evaluates the default value on Ok and Err. The op function in unwrap_or_else can be thought of as “lazy” or “evaluated when needed” - it only runs when the value returned is an Err (as functions can only get called with their input parameters). Generally, only use constants or precomputed values with methods that are eager to prevent unnecessary evaluation.