Taking the Unhappy Path with Result, Option, unwrap and ? operator in Rust
Tags: rust
Published: 2020-06-05 21:52:29

As I'm learning Rust, I noticed that these 4 concepts `Result`, `Option`, `unwrap`, `?` are often discussed in conjunction with each other. In this post, I'd like to document and share the thought process I have been going through. This thought process has helped me understanding and how to be more idiomatic in Rust.

### Option
Although Rust has a concept of [null](https://doc.rust-lang.org/std/ptr/fn.null.html), using null is not a common pattern in Rust. So let's say we have to write a function that, given the name of an mobile OS, the function will return the name of the store. If the string literal `iOS` is passed as input to the function, it will return `App Store`. If `android`, then `Play Store`.  Any other input is considered invalid. In most other languages, we can choose to return `null` or a string literal `invalid` or something like that. But doing so is not the Rust way.

One idiomatic way in Rust to implement such function is to return an [Option](https://doc.rust-lang.org/std/option/enum.Option.html). An `Option` or to be exact an `Option<T>` is a `generic` and can be either `Some<T>` or `None` (From here on, I will mostly drop the `generic type parameter T` so the sentences do not get so cluttered).  Rust refers to 'Some' and 'None' as `variants` (which does not have any equivalent in other languages, so I just don't get so hanged up on trying to define what a `variant` is). In our example, in the `happy paths`, the function returns the string literal "App Store" or "Play Store" wrapped in a `Some` variant. In the `unhappy path`, the function returns `None`.   

```
fn find_store(mobile_os: &str) -> Option<&str> {
    match mobile_os {
        "iOS" => Some("App Store"),
        "android" => Some("Play Store"),
        _ => None
    }
}
```

To consume `find_store()`, we can call it as follow:

```
fn main() {
    println!("{}", match find_store("windows") {
        Some(s) => s,
        None => "Not a valid mobile OS"
    });
}
```

You can try to run the above example in the [playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=21e4e13b2bf42d9342ef7ac7d4227c37)

### Result
[Result](https://doc.rust-lang.org/std/result/enum.Result.html) is related to `Option` in Rust in that `Result` or to be exact `Result<T, E>` is a richer version of `Option`. 

`Result<T, E>` could have one of [two outcomes](https://doc.rust-lang.org/stable/rust-by-example/error/result.html):

- `Ok(T)`: An element `T` was found
- `Err(E)`: An error was found with element `E`

as opposed to `Option<T>` previously seen that can contains `Some<T>` or `None`. `Result` has information about the error which is absent in `Option`.  

Let's look at an example of a function that returns a `Result`. This function is from the [serde_json](https://docs.serde.rs/serde_json/de/fn.from_str.html) [crate](https://crates.io/crates/serde_json) which is used to parse JSON. The signature of this function is

```
pub fn from_str<'a, T>(s: &'a str) -> Result<T, Error> 
where
    T: Deserialize<'a>, 
```

Assume we want to parse the following `String`

```
let json_string = r#"
        {
            "name": "John Doe",
            "age": 43,
            "phones": [
                "+44 1234567",
                "+44 2345678"
            ]
        }"#;
```

to a person object of this `struct`

```
#[derive(Serialize, Deserialize)]
struct Person {
    name: String,
    age: u8,
    phones: Vec<String>,
}
```

the code that parses the `json_string` to the `Person` object is as follow:

```
let p:Person = match serde_json::from_str(json_string) {
    Ok(p) => p,
    Err(e) => ... //we will discuss what goes here next 
};
```

The happy path is obvious. But let's say there's a typo in the input `json_string`, `match` will send the program flow to the `Err(e)` clause. 

When the `Err` is encountered, there are 2 actions that we can take:

1. `panic!` (more about panic) [here](https://doc.rust-lang.org/book/ch09-01-unrecoverable-errors-with-panic.html) and [here](https://doc.rust-lang.org/rust-by-example/std/panic.html)
2. `return` the `Err`

Back to our example, here's the full code if we decide to `panic!` 

{% gist https://gist.github.com/khle/ed2c8623aa41f101203d567071f0fa7a %}

[Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=b2f48fe434cb589d6ece2c2d1e2cec39) 

or if we want to return `Err`

{% gist https://gist.github.com/khle/1404e57b2acecb5e886b65b015c8f2d7 %}

[Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=66ef4c063f6437aaedb5f53fcc7d5bd5)

### unwrap
In the above example, let's consider at the case where we decide to `panic!`. The lines of code above

```
let p: Person = match serde_json::from_str(data) {
        Ok(p) => p,
        Err(e) => panic!("cannot parse JSON {:?}, e"), //panic
    }
```

is very verbose. Happy path is handled normally as expected, no discussion there. And when the `Err` is encountered, the above code `panic!`. We can replace the above verbose code with

```
let p:Person = serde_json::from_str(data).unwrap();
```

`unwrap` is OK to use if we know for sure that the input `json_string` will always be parsable to the `Person` struct, i.e. always happy path or if in case the `Err` is encountered, the program can't go on and it's unrecoverable. During development, when we're more concerned with getting the general flow of the program, `unwrap` can also be used as quick prototype.
 
So `unwrap` is implicit `panic!`. Although there's no difference with the more verbose counterpart, the danger is it's implicit, so sometimes it's not really what really want to do.

Regardless, if we need to call `panic!`, the code is shown as below:

{% gist https://gist.github.com/khle/c9745b87df2a412f7610ba1400d40689 %}

[Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=1d95f381008fb71dc95544be48f58a3e) 

### `?` operator

As opposed to `panic!` when the `Err` is encountered, we can `return` the `Err`. Not every `Err` is unrecoverable, so there's no reason to `panic!`. The verbose code to return `Err` is shown again below:

```
let p: Person = match serde_json::from_str(data) {
        Ok(p) => p,
        Err(e) => return Err(e.into()),
};
```

`?` offers us a more concise way to replace the verbose code above:

```
let p:Person = serde_json::from_str(data)?;
```

So the entire program is showm here

{% gist https://gist.github.com/khle/9349426eb28ba420d06801981fd172a4 %}

[Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=d99b3a46ae83b22938b87a637386c0c0)

### Unpacking Option with unwrap and ?
Just as we could use `unwrap` and `?` with `Result`, we can also use `unwrap` and `?` with `Option`

If we `unwrap` an `Option` that has the underlying value of `None`, the program `panic!`

Here's an example of how to unwrap an `Option`:

{% gist https://gist.github.com/khle/00b6904c085bcc525fb1bdde0581ed53 %}
[Playground](https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=ee3bdadbd11d2fbdcf3c84a0c8c87ee6)

Similarly, we can also unpack an `Option` with the `?` operator. If the outcome is `None`, the program will terminate whatever function is being executed and return `None`.