I recently started learning Rust and faced my biggest obstacle: Errors.
I’ve mostly avoided error handling because it honestly didn’t make any sense, if you’re the one writing code, then write it in a way that you won’t have to handle errors, why go the extra mile to handle them?
Terrible. Terrible idea.

Every popular builtin/library that you use contains a lot of error handling mechanisms which help you decide what to do when an error is returned.

For example, in Rust, when you try to add {integer} and {float}, you get an error. It may not be a serious problem in other languages but Rust takes it very seriously, and rightfully so.

This brings me to questioning the difference between exceptions and errors, coming from Python, exceptions are basically events that disrupt the normal flow of a program.

For example, if you ask user for a numerical input but the user inputs a character, or a string, then you are bound to get a ValueError.

Now exceptions can be easily handled by anticipating what may or may not happen given a certain scenario i.e., they are recoverable.
But errors are pesky little things that may or may not be recoverable.

The thing to keep in mind is, recoverable errors allow retrying an operation after perhaps reporting it. file not found error is one such example.
Unrecoverable errors are likely due to you, trying to access an out of bounds index, that’s bad. So we would want to immediately terminate the program if that happens.

Rust requires you to acknowledge the possibility of an error and take some action before your code will compile.1

Unrecoverable errors

Rust has a macro for situations where you know that the recovery impossible or the code is faulty.
The panic macro essentially unwinds, i.e., going back and cleaning up.

For example:2

fn main() {
    panic!("crash and burn");
}

will generate the following:

$ cargo run
   Compiling panic v0.1.0 (file:///projects/panic)
    Finished dev [unoptimized + debuginfo] target(s) in 0.25s
     Running `target/debug/panic`
thread 'main' panicked at 'crash and burn', src/main.rs:2:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Using the RUST_BACKTRACE=1 before cargo run will provide a flow of sorts which will help you find out which one of your files caused the program to panic.

Some other examples such as trying to access an invalid index in an array/vector will also cause the program to panic.

Now while this is nice, where must one use this panic! macro?
Generally, you will use panics in development environment, that includes testing, demonstration and the like. There are methods that will panic for you, unwrap method is one such example.

.unwrap() is “hey, I already know that this cannot fail, so just trust me”
— dawnofmidnight3

Recoverable Errors

Hang on tight because this is a big one, and a very deep one.

Let’s warmup with what we’ve learned so far about the Result enum and work our way up to custom error types.

Looking at Result’s documentation, we see that it implemented as:

pub enum Result<T, E> {
    Ok(T),
    Err(E),
}

It has two variants, Ok(T) upon success and Err(E) upon failure. It essentially “contains” our results.

Let’s check it out with a quick example4:

pub fn generate_nametag_text(name: String) -> ??? {
    if name.len() > 0 {
        ???
    } else {
        ???
    }
}

I’m sure the sentiment of the code is pretty clear, return something positive when a name is inserted and perhaps an error mentioning that no name was inserted.
This is the exact scenario where you would use Result.

To be more descriptive, let us return String upon both success and failure.
We could do it like so:

pub fn generate_nametag_text(name: String) -> String {
    if name.len() > 0 {
        return format!("We got a name: {}", name);
    } else {
        return "We did not get a name!!".to_string();
    }
}

While this is very nice and does the job perfectly by fulfilling our requirements, it doesn’t provide much control to the function calling generate_nametag_text, how should the calling function determine whether it was a success or a failure? By comparing the entire string?
What if a developer decides that, oh no, maybe the error message had an extra ! so I should probably remove it, and the whole code breaks because it depends on that one character.

That is not very nice, which introduces us to Result enum, which could return an Ok if a condition was satsified, Err or an error otherwise.

And so, our program can be rewritten like:

pub fn generate_nametag_text(name: String) -> Result<String, String> {
    if name.len() > 0 {
        return Ok(format!("We got a name: {}", name));
    } else {
        return Err("We did not get a name!!".to_string());
    }
}

Now the function will return a Result enum, which is essentially either Ok or Err along with the exact same message, so now the caller of generate_nametag_text doesn’t have to depend on every character of the message/string, but rather, can work with Ok and Err, which provides a lot better control than returning a measly string.

TODO: Propagating Errors

basically~ use Result<T, E> if you can recover, and panic! when you cannot (and for tests)
— dawnofmidnight5