Rust #4: Options and Results (Part 2)
Tags: rust
Published: 2021-07-10 15:54:54

Last week I wrote about basic use of `Option`, `Result` and creating and using errors derived from `std::error::Error`. But both `Option` and `Result` have a host of methods that I wanted to explore and describe in today's post. Below is an overview of some of the `Option` methods I want to talk about:

| Method            | Use Description                                             | Return Type          |
|-------------------|-------------------------------------------------------------|----------------------|
| and               | Testing two `Option`s are not `None`                        | Option\<U>           |
| and_then          | Chaining `Option`s                                          | Option\<U>           |
| expect            | Panic if None                                               | T                    |
| filter            | Filter the `Option` with a predicate                        | Option\<T>           |
| flatten           | Removes nested `Option`s                                    | Option\<T>           |
| is_none           | Test the `Option` type                                      | bool                 |
| is_some           | Test the `Option` type                                      | bool                 |
| iter              | Iterate over its single or no value                         | an iterator          |
| iter_mut          | Iterate over its single or no value                         | an iterator          |
| map               | Transform the value into another                            | Option\<U>           |
| map_or            | Transform the value into another                            | U                    |
| map_or_else       | Transform the value into another                            | U                    |
| ok_or             | Transform the `Option` to a `Result`                        | Result<T,E>          |
| ok_or_else        | Transform the `Option` to a `Result`                        | Result<T,E>          |
| or                | Provide a new value if `None`                               | Option\<T>           |
| or_else           | Provide a value if `None`                                   | Option\<T>           |
| replace           | Change the value to a `Some` while returning previous value | Option\<T>           |
| take              | Change the value to `None` while returning original         | Option\<T>           |
| transpose         | Change `Option` of `Result` to `Result` of `Option`         | Result<Option<T>, E> |
| unwrap            | Extract value                                               | T                    |
| unwrap_or         | Extract value                                               | T                    |
| unwrap_or_default | Extract value                                               | T                    |
| unwrap_or_else    | Extract value                                               | T                    |
| xor               | Return one of the contained values                          | Option\<T>           |
| zip               | Merge `Options`                                             | Option<(T, U)>       |

This is a non-exhaustive list. And below is an overview of the `Result` methods I want to talk about:

| Method            | Use Description                                     | Return Type         |
|-------------------|-----------------------------------------------------|---------------------|
| and               | Testing two `Results` are not errors                | Result<U,E>         |
| and_then          | Chaining `Results`                                  | Result<U,E>         |
| err               | Extract the error                                   | Option\<E>          |
| expect            | Panic if `Err`                                      | T                   |
| expect_err        | Panic if `Ok`                                       | E                   |
| is_err            | Test the `Result` type                              | bool                |
| is_ok             | Test the `Result` type                              | bool                |
| iter              | Iterate over its single or no value                 | an iterator         |
| iter_mut          | Iterate over its single or no vlaue                 | an iterator         |
| map               | Transform the value into another                    | Result<U,E>         |
| map_err           | Transform the value into another                    | Result<T,E2>        |
| map_or            | Transform the value into another                    | U                   |
| map_or_else       | Transform the value into another                    | U                   |
| ok                | Converts `Result` into `Option`                     | Option\<T>          |
| or                | Provide a new `Result` if `Err`                     | Result<T,E2>        |
| or_else           | Provide a new `Result` if `Err`                     | Result<T,E2>        |
| transpose         | Change `Result` of `Option` to `Option` of `Result` | Option\<Result\<T,E>> |
| unwrap            | Extract value                                       | T                   |
| unwrap_err        | Extract error                                       | E                   |
| unwrap_or         | Extract value                                       | T                   |
| unwrap_or_default | Extract value                                       | T                   |
| unwrap_or_else    | Extract value                                       | T                   |

You can see from the above tables thit's the absense of a result `Option` and `Result` have similar methods and act in similar ways. This is not surprising if you think about it because both can return results or a non-result. In `Option`'s case, the non-result is an absence of a result (`None`) and in `Result`'s case, the non-result is an error.

Both have logical operations that combine each other: `and`, `or` and in `Option`'s case, `xor`.

Both can be treated like an iterator with one or no values.

Both can transform their values via the `map` family of methods and extract values via the `unwrap` methods.

Also, you may notice some common suffixes on the functions, particularly `or` versus `or_else`. The difference between these functions is how they provide a default value. The `or` signifies that if there is no result (i.e. `None` or `Err`), then here is one I provide for you! The `else` part signifies that I will provide the default value, but via a function. This allows a default result to be provided lazily. Passing a value to an `or` type method will mean it is evaluated regardless of the original value. This is because all parameters in Rust are evaluated before `or` is called. Because `or_else` uses a function to provide the default value, this means it is not evaluated unless the original value is an `None` or `Err`.

## Good and Bad

Because I will be talking about `Option` and `Result` generically as they share so much in common, I will use different terminology for the types of values they can have.

For `Some` and `Ok` values, I will call them good values.

For `None` and `Err` values, I will call them bad values.

There is no semantic meaning to the terms other than to distinguish between them. I could use positive and negative, or result and non-result. But the words good and bad are easier to type!

## Extracting Good Values and Result Errors

A common use pattern is to extract the good value regardless of whether it is good. This is done by the `unwrap` family of methods. How they differ is what happens when the value is not good. If it is a good value, they just convert the value into the contained value. That is, an `Option<T>` or `Result<T,E>` becomes a `T`.

If you want to panic on a bad value, just use `unwrap`. I wouldn't recommend using this as panicking is so user unfriendly, but it's good for prototype work and for during development.

If you want to provide a default value, then there are two ways to do this: eagerly; and lazily. The `unwrap_or` and `unwrap_or_else` do this and I've already explained what `or` and `or_else` means above.

If you want to provide the type's default value via the `Default` trait, use `unwrap_or_default`.

Finally, `Result` has a couple more unwrapping methods for errors: `err` and `unwrap_err`. `err` will return an `Option<E>` value, which is None if no error occurred. `unwrap_err` extracts the error value.

```rust
let maybe_name: Option<String> = get_dog_name();

let name1: String = maybe_name.unwrap();
let name2: String = maybe_name.unwrap_or(String::from("Fido"));
let name3: String = maybe_name.unwrap_or_else(|| String::from("Fido"));
let name4: String = maybe_name.unwrap_or_default();

let maybe_file: Result<std::fs::File, std::io::Error> = File::open("foo.txt");
let file_error: std::io::Error = maybe_file.unwrap_err();
let maybe_file_error: Option<std::io::Error> = maybe_file.err();
```

## Iteration and Transforming Values

Both `Option` and `Result` can be treated as containers that have one or zero values in it. By iterating over a single `Ok` or `Some` value, this opens up `Option`s and `Result`s to all the iterator functionality.

So `iter` provides the `&T` and `iter_mut` provides the `&mut T` on the "collection".

Some of the iterator methods have been brought into `Option` and `Result`, removing the requirement to convert to an iterator first. This is the `map` family of methods and for `Option` there is `filter`.

`map` calls a function that converts the good value of one type to a good value of another. The function moves the wrapped value into a function that returns a new one, which doesn't even have to share the same type. So it converts an `Option<T>` to a `Option<U>` or a `Result<T,E>` to a `Result<U,E>`. For bad values, it remains the bad value with no transformation.

`map_or` and `map_or_else` extends `map`'s functionality by provided a default value in the case of a bad value. This follows the same `or` and `or_else` functionality as described above. However, the function you provide for the default in the `Result`'s case is provided with the error value.

`Result` has one more version for transforming errors. This is `map_err` and is often used, for example, to adapt standard errors to your own.

```rust
struct Dog {
    name: String,
    breed: Breed,
}

let maybe_name: Option<String> = get_dog_name();

let dog1: Option<Dog> = maybe_name.map(|dog_name| Dog {
    name: dog_name, 
    breed: Breed::Labrador 
});
let dog2 = maybe_name.map_or(
    Dog {
        name: String::from("Fido"),
        breed: Breed::Labrador,
    },
    |dog_name| Dog {
        name: dog_name,
        breed: Breed::Labrador,
    }
);
let dog3 = maybe_name.map_or_else(
    || Dog {    // This lambda is passed an error argument in Result case.
        name: String::from("Fido"),
        breed: Breed::Labrador,
    },
    |dog_name| Dog {
        name: dog_name,
        breed: Breed::Labrador,
    },
);

let maybe_file: Result<File, std::io::Error> = std::fs::File::open("foo.txt");
let new_file: Result<File, MyError> = maybe_file.map_err(|_error| Err(MyError::BadStuff));_
```

## Querying Value Types

`Option` provides `is_some` and `is_none` to quickly determine if it contains a good or bad value.

`Result` also provides `is_ok` and `is_err` for the same reasons.

There really isn't much to say about this. I would add that these are not used that often because the tests are implicit with the `if let` and `match` syntaxes.

```rust
let maybe_name = get_dog_name();
match maybe_name {
    Some(name) => Dog { name, breed: Breed::Labrador },
    None => Dog::default(),
}

// instead of:
let dog = if maybe_name.is_some() {
    // You still need to deconstruct the value here so the 
    // is_some check is redundant.
    if let Some(name) = maybe_name {
        Dog {
            name,
            breed: Breed::GermanShepherd,
        }
    } else {
        Dog::default()
    }
};
```

They are really only useful if you need to convert an option or result to a boolean.

## Logical combinations

Both `Option` and `Result` provide `and` and `or` methods that combine two values according to their logical rules.

With `and`, both values need to be good, otherwise the result is bad. Also, when all values are good, the result is the final good value. This makes `and` a really good way of chaining operations if you don't care about the results of anything but the last operation. For example:

```rust
// All these operations must be good
fn get_name(person: &Person) -> Option<String> { ... }
fn clone_person(person: &Person, name: String) -> Option<Person> { ... }

// We only create a new clone if the first clone has a name.  For some
// reason unnamed clones are not allowed to be cloned again!
let new_person = get_name(old_person).and(clone_person(old_person, "Brad"));

// new_person will either be None or Some(Person)
```

But even more useful is the method `and_then`. This allows you to chain operations together but passing the result of one to the function of the next.

For example, perhaps I want to try to open a file, and read the first line. Both of these operations can fail with a bad `Result`. `and_then` is perfect for this, because I only care about the first line and none of the intermediate data such as the open file:

```rust
use std::result::Result;
use std::fs::File;

fn read_first_line(file: &mut File) -> Result<String> { ... }

let first_line: Result<String> = File::open("foo.txt").and_then(|file| {
    read_first_line(&mut file)
});
```

The `or` method will check the first result and will return that result only if it is good. If it is bad, it will return the second result. This is useful for finding alternative operations. If one fails, then let's try the other.

```rust
struct Disease { ...  }

fn get_doctors_diagnosis() -> Option<Disease> { ... }
fn get_vets_diagnosis() -> Option<Disease> { ... }

// We're desperate!  Let's ask a vet if the doctor doesn't know.
let disease: Option<Disease> = get_doctors_diagnosis().or(get_vets_diagnosis());
```

Notice how `or` differs to `and` in that all operations must wrap the same type. In the `and` example, the first operation wrapped a `File`, then resulte in a wrapped `String`. This is possible due to the nature of the logical AND. As soon as one operation is bad, all results are bad. This is not so for logical OR. Any operation could be bad and we will still get a good result if at least one of them is good. This means that all wrapped values must be the same. In the example, all wrapped values were of type `Disease`.

`Option` provides one more logical combination and that is the logical XOR operation. The result is good if and only if only one of the operations is good. You can either take this result or the other, but not a combination of both.

## `and_then` versus `map`

It may not be obvious, but `and_then` and `map` do very similar things. They can transform a wrapped value into another. In the example above, `and_then` changed an `Option<File>` into an `Option<String>`. With the `map` example, we changed an `Option<String>` into an `Option<Dog>`. But they do differ and I want to talk about how they differ.

Both methods take a function that receives the wrapped value if good but the difference is in what those functions return.

In the case of `and_then` it returns a value of type `Option<U>`, whereas `map` returns a value of type `U`. This means that `and_then` can change a value from a good one to a bad one but `map` cannot; once good always good. `map` is purely for the transformation of a wrapped value to another value and even type but not goodness. That is, that transformation step cannot fail by becoming `Err` in the case of `Result`, or `None` in the case of `Option`. With `and_then` the transformation can fail. The function passed can return `None` or an `Err`.

It took a while for it to click for me why you use one over the other. I hopes this helps to clarify the reasons.

## Converting between Option and Results

Because these types are used in very similar circumstances, it is often useful to convert between them. Let's talk about how we do this.

### Some(T) -> Ok(T), None -> Err(E)

We have an option and we want to convert to a result.  You could use a `match`:

```rust
match opt {
    Some(t) => Ok(t),
    None => Err(MyError::new()),
}
```

That's a little verbose, but you can use `ok_or` and `ok_or_else` to provide the error if the option is `None`:

```rust
let res = opt.ok_or(MyError::new());
let res = opt.ok_or_else(|| MyError::new());
```

### Ok(T) -> Some(T), Err(E) -> None

Now we have a result and we want to convert to an option.  Again with `match`:

```rust
match res {
    Ok(t) => Some(t),
    Err(_) => None,
}
```

`Result` uses a method `ok` to do the conversion:

```rust
let opt = res.ok();
```

### Transposition and Flattening

Both `Result` and `Option` are container types that wrap a value of type `T`. But that type `T` can just as well be a `Result` and an `Option` too.

Transposition is the operation to swap a `Result` and `Option` in a nested type. So, for example, a `Result<Option<T>>` becomes a `Option<Result<T>>` or vice versa. Both `Result` and `Option` offer a `transpose` method to do that swapping.

Also, it is useful to convert a `Result<Result<T,E>,E>` into a `Result<T,E>`, or convert an `Option<Option<T>>` into an `Option<T>`. This operation is called flattening and both methods offer a `flatten` method to do so. But, as of writing, `Result::flatten` is only available in nightly builds and so therefore, it has not been stablised yet. This is why this article only shows `flatten` in the `Option` table at the beginning.

## Miscellaneous Operations

We've covered the lion's share of the methods that are used with `Option`.

Firstly, `replace` and `take` are used to move values in and out of the `Option`. `replace` transfers ownership of a value into the `Option` and returns the old value so something else can own it. This is useful to make sure the option is not in a valid state. You cannot just move its value out without replacing it with a new value. The borrow checker will not let you do that. Quite often this is used for options within structures.

It is very common that when you do a replace, you set the option to `None` as you extract the value you want. That is, you want to do something like this:

```rust
let mut x = Some(42);

// Extract the 42 to another owner, but now make x own `None`.
let extracted_value = x.replace(None)
```

But, unfortunately, `replace` takes a value of type `T`, meaning you can only replace it with a `Some` variant. `Option` provides another method `take` to do this:

```rust
let mut x = Some(42);
let extracted_value = x.take();  // x will be None after this.
```

## Summary

I hope I have provided a reasonable guide to using `Option` and `Result` effectively over the past two articles. It's a lot to take in and I will certainly be referring to my own articles to help me remember how I should use them.

We looked at what they are and how to create and use errors. We looked at methods to transform values, extract values, logically combine, manipulate structure and convert between them. Rust standard library provides a lot of functionality. There are more methods that I didn't go into, mainly because they were with advanced or unstable.

Next week I will write about naming conventions in the Rust Standard Library. As you have seen here, there are common suffixes that describe what the function may do. In the Standard Library there are prefixes too that help convey concepts to functions that share them. I'd like to understand them better and so I can understand what the function might be doing at a glance and perhaps reused them for my own code so that others can understand it too.