30 Days of Rust - Day 21
Tags: rust, devjournal, learning, beginners
Published: 2023-05-25 20:55:50

Hi folks, today was my first day back at work after a few days off. I can't say I'm thrilled to be back, after all I had a lovely vacation. But I'm excited to get back to the blog 💪 

Those of you who've been here since day 1 may feel like the last few blog posts have simply been going over concepts I'd already covered in more detail. That is in part true: after pivoting to work on the Rust book, I have indeed revisited some concepts. But today's blog is hopefully going to bring some novel ideas to the table 🙌
 
## Yesterday's questions answered
*No questions to answer*

## Today's open questions
*No open questions*

## Care about your mistakes
As developers (and humans, I guess) we all make mistakes, and hopefully as kids our parents made it clear to us that *learning* from your mistakes is a virtuous trait. Some of the biggest mistakes we make when coding may remain totally unknown to us. Untested code that "runs" without errors may be doing something unpleasant without us knowing, especially when our code doesn't have an obvious human interface (like a UI). 

This is often a problem in my profession, data engineering. Input data can have millions of possible results, so exhaustive testing is a no go. But incoherent and improbable outcomes can often still be consumed by downstream systems, so a green pipeline can be very misleading.

No programming language can prevent engineering teams from creating these kinds of very much human problems. They can, however, offer a interface between human and machine in the form or errors or exceptions. As we're talking about Rust, we refer to these as *errors*.

### Rust will make you care (or cry) about errors
Before we delve into Rust's various error-handling APIs, I think it's useful to use the Rust book's understanding of errors:
> Some errors are recoverable, some are not. When an error is unrecoverable, your program will *panic*.

Now you may think, that's very vague. But it underpins something important about error handling in Rust: you must (almost) always be explicit about declaring an error as unrecoverable. Let's take the example from the Rust book: 

```rust
let greeting_file_result = File::open("hello.txt");
```

And compare it with Python:
```python
greeting_file = pathlib.Path("hello.txt").open("r")
```

On first glance, these snippets appear very similar, almost identical. But what happens if `hello.txt` doesn't exist? In Python, the code will throw a FileNotFoundError. In Rust, your code will compile and run error-free. This is because Rust returns a [Result type](https://doc.rust-lang.org/std/result/). This wraps the equivalent NotFound error, so that you can choose when and where to handle it in your code.

If you actually want to get hold of the data in the file in Rust, you *must* extract either the error or the value from the Result type. This means your code must explicitly handle an error for it to compile. The ways of doing this vary in verbosity. Let's examine the recommended solution for errors that for our program should be deemed *unrecoverable*.

Imagine `hello.txt` contains information vital to your application and you want to make this obvious in the code itself (Uncle Bob wouldn't approve of a comment, after all). Let's refactor the above code snippets to consider this:
```rust
let greeting_file = File::open("hello.txt").expect("Hello file must be available at application startup.");
```

```python
greeting_file = pathlib.Path("hello.txt")
if not greeting_file.exists():
	raise Exception("Hello file must be available at application startup.")
greeting_file = greeting_file.open("r")

# OR

try:
	greeting_file = pathlib.Path("hello.txt").open("r")
except FileNotFoundError as e:
	raise e
```

In Rust the `.expect` method of a Result type allows you to call the `panic!` macro with a custom error message. The resultant code reads very nicely. Compare it to your options in Python. Both require extra lines of code and nesting. Also, if style rules are not defined, you could have a code base full of different error handling approaches.

The point is that Rust here is pushing you towards better practices when coding: think about errors, be explicit, and propagate errors in a readable way.

### Reducing Return type boilerplate
The above example demonstrates how to induce a panic. Realistically, we wouldn't want to do this in most cases where a function returns a result type. Most of the time we'll expect there to be a value. 

So extracting a value via a match statement can get tedious and long. Consider the refactored example again refactored using a match statement:
```rust
let greeting_file_result = File::open("hello.txt")
let greeting_file = match greeting_file_result {
	Ok(file) => file,
	Err(error) => create_hello_file(),
}
```

Imagine we wanted to create a file instead of inducing a panic if the file doesn't exist. Image then that the `create_hello_file` function also return a Result type. You end up with a pretty ugly nested match statement. *Enter `?` operator*.

As such a case is so common in Rust code, the language has `?` operator. Its usage looks similar to JavaScript `?` operator. The difference is that `?` catches errors instead of `undefined`. Thus if we chained the `create_hello_file` function with `?`, it would propagate any errors up to the match statement.

The chain operator provides a way to combine function or method calls such that when, for each call, the expected result is returned, the calling function will return the result. Simultaneously, any errors are automatically returned with the result, assuming that the error type is compatible with the calling function's signature.


### Differentiating between errors
You can use enums to create kinds or errors. This means that you can match specific error types in match statements and do different things depending on what the error is. You can also use if statements to do the same thing.

### When to return Results
This is pretty simple:
> When in doubt, return a result rather than panicking.
> This makes your code more reusable, as the calling function must decide what to do with the result.
> The exception is when arguments don't mean requirements (ints being positive, for example). Then you should panic.