There are even more ways to combine Resultss!

and

and is similar to and_then except a default Result is returned on an Ok instance:

pub fn and<U>(self, res: Result<U, E>) -> Result<U, E> {
    match self {
        Ok(_) => res,
        Err(e) => Err(e),
    }
}

Notice that the value inside the Ok instance is never used:

Ok(_) => res,

Since res is eager it will get evaluated as soon as and is called. Values for res should only be constants and precomputed values.

In summary:

// pseudocode
// Given: a Result<T, E>
// Return typr: Result<U, E>
// res is eager

Ok(_:T)  -> res:Result<U, E> -> Result<U, E>  // `Ok` value type changes from `T` from `U`
Err(e:E) -> Err(e)           -> Result<U, E>  // Notice that the `Err` value type is fixed at: `E`

This can be useful when you only want to know if something succeeded or failed instead of needing to work on its value.

Take creating a directory and returning a Success value as an example.

enum FileCreation {
  Success,
  Failure
}

We can create a directory with the create_dir function from the std::fs module:

fn create_dir<P: AsRef<Path>>(path: P) -> io::Result<()>

Notice how this function returns a Result with a unit as the success value.

If we use map to complete the example use case:

fn create_directory_map(dir_path: &Path) -> io::Result<FileCreation> {
  create_dir(dir_path)
    .map(|_| { // We ignore the value from create_dir
        FileCreation::Success
    }) // Result<FileCreation>
}

We have to ignore the previous success value in map (as we can’t do anything useful with unit). This is a little verbose and we can trim it down with and:

fn create_directory_and(dir_path: &Path) -> io::Result<FileCreation> {
  create_dir(dir_path)
    .and(Ok(FileCreation::Success))  // Result<FileCreation>
}

or

If you wanted to try an alternative Result on Err and you didn’t care about the error value, you could use or. or is defined as:

 pub fn or<F>(self, res: Result<T, F>) -> Result<T, F> {
   match self {
     Ok(v) => Ok(v),
     Err(_) => res,
   }
 }

In the definition above the value res is used only when there is an Err instance. If the Result is an Ok instance, its value is returned.

Since res is eager it will get evaluated as soon as or is called. Values for res should only be constants and precomputed values.

In summary:

// pseudocode
// Given: Result<T, E>
// Return type: Result<T, F>
// res is eager

Err(_:E) -> res:Result<T, F>  -> Result<T, F> // The `Err` value type changes from `E` to `F`
Ok(t:T)  -> Ok(t)             -> Result<T, F> // `Ok` value type is fixed: `T`

It’s important to note that res dictates the final Err type returned from or and that the type inside the Ok constructor doesn’t change. We’ll see that come into play in the example below.

Take the example of reading some configuration from a file or returning a default.

We can read from a file with the read_string function in the std::fs module:

pub fn read_to_string<P: AsRef<Path>>(path: P) -> Result<String>

We can read the config file or return a default with:

fn read_config(config_file: &str) -> Result<String, MyError> {
  use std::fs;
  let default_config: String = "verbose=true".to_owned();

  fs::read_to_string(config_file) // Result<String, std::io::Error>
    .or(Ok(default_config)) // Result<String, MyError>
}

The function res, passed to or dictates the final Err type. In the above example our error type has changed from std::io::Error to MyError. Also when chaining multiple or calls, the final res block dictates the final Result type. In the case of or chaining, the Ok type is fixed but the Err type can vary!

or_else

or_else is similar to or with the exception that you get access to the error type E and the op parameter is lazy:

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

The function op takes in the Err type E and returns a Result with the same success type T and a new error type F:

FnOnce(E) -> Result<T, F>

In summary:

// pseudocode
// Given: Result<T, E>
// Return type: Result<T, F>

Err(e:E) -> op(e)  -> Result<T, F> // `Err` value type goes from `E` -> `F`
Ok(t:T)  -> Ok(t)  -> Result<T, F> // `Ok` value type is fixed: `T`

Here’s an example of where we can try one of several parse functions until we find one that succeeds.

Given a common error type MyError and a common success type MyResult:

#[derive(Debug)]
struct MyError(String);

#[derive(Debug)]
enum MyResult {
  N(u32),
  B(bool),
  S(String),
}

And functions to parse numbers and booleans:

fn parse_number(value: &str) -> Result<u32, ParseIntError> {
  u32::from_str(value)
}

fn parse_bool(value: &str) -> Result<bool, ParseBoolError> {
  bool::from_str(value)
}

One thing to note is that both functions return different error types in Err: ParseIntError and ParseBoolError respectively.

How would we combine these functions into parsing a string slice into a type of MyResult? And we also don’t support converting a string that is all caps into MyResult. That would be an error.

Similar to or, the function op, passed to or_else dictates the final Err type. When chaining multiple or_else calls, the final op call dictates the final Result type. In the case of or_else chaining, the Ok type is fixed but the Err type can vary.

Note that we don’t need to align the error types here as mentioned before because the Result passed to or_else would change the final Err type as required.

Here’s one way we could do it:

fn parse_my_result(value: &str) -> Result<MyResult, MyError> {
  parse_number(value)
    .map(|n| MyResult::N(n))
    .or_else(|_|
      parse_bool(value)
        .map(|b| MyResult::B(b))
    )
    .or_else(|_|
      if value.to_uppercase() == value {
          // We don't support full screaming caps
          Err(MyError(format!("We don't support screaming case: {}", value)))
       } else {
        Ok(MyResult::S(value.to_owned()))
       }
    )
}

We could use it like:

let r1: Result<MyResult, MyError> = parse_my_result("123"); // Ok(N(123))
let r2: Result<MyResult, MyError> = parse_my_result("true"); // Ok(B(true))
let r3: Result<MyResult, MyError> = parse_my_result("something"); //Ok(S("something"))
let r4: Result<MyResult, MyError> = parse_my_result("HELLO"); //Err(MyError("We don't support screaming case: HELLO"))

How the Err type changed between ParseIntError, ParseBoolError to MyError can be a bit harder to see. Here’s a more detailed example of the above:

fn parse_my_result_2(value: &str) -> Result<MyResult, MyError> {
  let p1: Result<MyResult, ParseIntError> = parse_number(value)
    .map(|n| MyResult::N(n));

  let p2: Result<MyResult, ParseBoolError> =  parse_bool(value)
        .map(|b| MyResult::B(b));

  let p3: Result<MyResult, MyError> =
    if value.to_uppercase() == value {
        // We don't support full screaming caps
        Err(MyError(format!("We don't support screaming case: {}", value)))
     } else {
      Ok(MyResult::S(value.to_owned()))
     };

    let r1: Result<MyResult, ParseBoolError> = p1.or_else(|_| p2);
    let r2: Result<MyResult, MyError> = r1.or_else(|_|p3);

    r2
}

Would could also write the above example with or since we have precomputed all the values before using or_else:

fn parse_my_result_3(value: &str) -> Result<MyResult, MyError> {
  let p1: Result<MyResult, ParseIntError> = parse_number(value)
    .map(|n| MyResult::N(n));  // Already evaluated

  let p2: Result<MyResult, ParseBoolError> =  parse_bool(value)
        .map(|b| MyResult::B(b));  // Already evaluated

  let p3: Result<MyResult, MyError> =
    if value.to_uppercase() == value {
        // We don't support full screaming caps
        Err(MyError(format!("We don't support screaming case: {}", value)))
     } else {
      Ok(MyResult::S(value.to_owned()))
     }; // Already evaluated

    let r1: Result<MyResult, ParseBoolError> = p1.or(p2); // Using or
    let r2: Result<MyResult, MyError> = r1.or(p3);  // Using or

    r2
}