Even prettier messages

There exists an even better crate derived from anyhow, which is called eyre. The idea of eyre is the same as anyhow: the error type is called eyre::Report (instead of anyhow::Error), and eyre::Result<T> can be used instead of anyhow::Result<T>.

In addition, reporters can be installed to display the eyre::Report errors in a particular way. For example, the [color-eyre](https://crates.io/crates/color-eyre) crate will display the messages with colors to facilitate error reading. All you have to do is call color_eyre::install()?; at the beginning of your main() function.

Exercise 7.a: add the eyre and color-eyre crates to your Cargo.toml, initialize color_eyre as explained above, and make your main() function return a eyre::Result<()>.

Now make some errors happen and see how beautifully they are rendered.

Some thoughts about error handling

In our case, we never use the fact that we can match on the various variant of our Error type. We could have used Result<T, eyre::Report> (or the equivalent eyre::Result<T>) as the return type of all our functions that can fail.

However, this is not always the case: if you are developing a library, your users might want to get precise information about an error you give them back. In this case, it is important to use an Error enumeration allowing the library user to match the error against the various cases.

But here, let's simplify our code:

Exercise 7.b: remove your Error type, and make every function that can fail return a eyre::Result<T> (where T is the type of a successful result).

However, you still have the problem of BadHttpResult which is not an existing error. Don't worry, you can return prematurely with a textual error by using the macro eyre::bail! with works like format!() or println!():

#![allow(unused)]
fn main() {
   // Return an error immediately with the given formatted text
   eyre::bail!("This returns an eyre::Report with {}", 42);
}

Adding context

You may have noticed that the error message describes what the error is but not what you were trying to do. For example, when a web page fails to be retrieved, the URL is not shown anywhere, or when the file cannot be created, the file name is not to be seen.

By adding use eyre::Context; near the beginning of a file, you import an extension trait of the Result type which allows some contextual information to be added.

Let us take the example of the write_to_disk() function:

#![allow(unused)]
fn main() {
fn write_to_disk(path: &str, data: Vec<String>) -> eyre::Result<()> {
    let mut f = std::fs::File::create(path)?;
    for l in data {
        f.write_all(format!("{l}\n").as_bytes())?;
    }
    Ok(())
}
}

Every intermediate Result can be enriched with a context if it contains an error before the error is returned:

#![allow(unused)]
fn main() {
fn write_to_disk(path: &str, data: Vec<String>) -> eyre::Result<()> {
    let mut f = std::fs::File::create(path).context(format!("Unable to create {path}"))?;
    for l in data {
        f.write_all(format!("{l}\n").as_bytes())
            .context(format!("Error while writing {path}"))?;
    }
    Ok(())
}
}

In case of an error, the context will be displayed:

$ cargo run
Error: 
   0: Unable to create /trending.txt
   1: Permission denied (os error 13)

Location:
   src/main.rs:45

[…]

Exercise 7.c: add a context to all errors before returning them.

Check by using a bad URL or a bad file name that the context is correctly printed and contains all the useful information.

Congratulations, you are now a master in Rust error handing.