Easier errors

You may have noticed that it is rather cumbersome to write all those From methods to convert existing errors into our own variant. Fortunately, a crate exists to make this job easier: thiserror.

After adding thiserror to your Cargo.toml

$ cargo add thiserror

you will be able to define your Error type as follows:

fn main() {
#[derive(Debug, thiserror::Error)]
enum Error {
    #[error("Network error: {0}")]
    ReqwestError(#[from] reqwest::Error),
    #[error("Wrong HTTP status code: {0}")]
    #[error("I/O error: {0}")]
    Io(#[from] std::io::Error),

Our Error type looks the same as before with a few additions:

  • It derives thiserror::Error. This is the heart of the thiserror crate and makes the other things below work. Behind the scenes, it uses "procedural macros" which is a way to transform some Rust code into another Rust code during the compilation.
  • The #[error] attribute indicates how to display the error. {0} refers to the first field. The fields could also be named, a field xyz would use {xyz} in the #[error] attribute.
  • The #[error] attribute on the field of a variant indicates that a From implementation should be generated from the type following #[from] to an Error.
  • Your Error type will implement the std::error::Error trait which allows proper error chaining: the system will know (and will be able to display) that the cause of a Error::ReqwestError is a reqwest::Error, which might also be caused by another deeper error, and so on.

Exercise 5.a: modify your Error type to use thiserror and remove your existing From implementations.

Check that everything still works, including when you make errors happens by changing the urls or the file name.