Ben Chuanlong Du's Blog

It is never too late to learn.

Error Handling in Rust

Things on this page are fragmentary and immature notes/thoughts of the author. Please read with your own judgement!

In [ ]:
:timing
:sccache 1

Tips and Traps

  1. The question mark ? is a sugar syntax ...

  2. eprintln!

  3. Result.expect is a more user friendly alternative to Result.unwrap. Result.expect allows you to define a customized error message. It is suggested that you use Result.expect instead of Result.unwrap where applicable. Of course, both Result.expect and Result.unwrap are discouraged and it is suggested that you use pattern matching and handle errors explicitly.

  4. Result.ok converts a Result object to an Option object. Symetrically, Option.ok_or converts an Option to a Result object.

  5. The recommended way of error handling in Rust is to define an enum that covers meaningful error cases for your library, implement Debug, Display and std::error::Error for that enum. This is fast/cheap (no heap allocation on error), and precise and easy to use.

  6. By default errors in Rust are checked (at compile time). However, you can get unchecked error using Box<dyn std::error::Error + 'static> with Send and Sync.

  7. The disscussion in The state of error handling in the 2018 edition suggests that thiserror (for libraries) + anyhow (for applications) is a good combination.

Rust Crates for Error Handling

thiserror

anyhow

miette

miette is a fancy diagnostic reporting library and protocol for us mere mortals who aren't compiler hackers.

In [2]:
:dep thiserror = "1.0.25"
:dep anyhow = "1.0.41"
In [3]:
use thiserror::Error;

#[derive(Error, Debug)]
pub enum ParseRankError {
    #[error("{0} is not a valid symbol for card rank!")]
    InvalidSymbol(char),
    #[error("{0} is not a valid integer for card rank!")]
    InvalidInteger(u8),
}
In [4]:
let err = ParseRankError::InvalidSymbol('m');
err
Out[4]:
InvalidSymbol('m')
In [5]:
err.to_string()
Out[5]:
"m is not a valid symbol for card rank!"
In [6]:
println!("{}", err);
m is not a valid symbol for card rank!
In [7]:
println!("{:?}", err);
InvalidSymbol('m')
In [8]:
println!("{:?}: {}", err, err);
InvalidSymbol('m'): m is not a valid symbol for card rank!
In [9]:
eprintln!("{:?}: {}", err, err);
InvalidSymbol('m'): m is not a valid symbol for card rank!
In [12]:
use std::io;
In [16]:
let err = io::Error::new(io::ErrorKind::Other, "oh no!");
err
Out[16]:
Custom { kind: Other, error: "oh no!" }
In [17]:
eprintln!("{:?}: {}", err, err);
Custom { kind: Other, error: "oh no!" }: oh no!
In [19]:
println!("{:?}", io::Error::last_os_error());
Os { code: 2, kind: NotFound, message: "No such file or directory" }
In [ ]:
use thiserror::Error;

/// WordCountError enumerates all possible errors returned by this library.
#[derive(Error, Debug)]
pub enum WordCountError {
    /// Represents an empty source. For example, an empty text file being given
    /// as input to `count_words()`.
    #[error("Source contains no data")]
    EmptySource,

    /// Represents a failure to read from input.
    #[error("Read error")]
    ReadError { source: std::io::Error },

    /// Represents all other cases of `std::io::Error`.
    #[error(transparent)]
    IOError(#[from] std::io::Error),
}
In [ ]:

In [ ]:
/// WordCountError enumerates all possible errors returned by this library.
#[derive(Debug)]
enum WordCountError {
    /// Represents an empty source. For example, an empty text file being given
    /// as input to `count_words()`.
    EmptySource,

    /// Represents a failure to read from input.
    ReadError { source: std::io::Error },

    /// Represents all other cases of `std::io::Error`.
    IOError(std::io::Error),
}

impl std::error::Error for WordCountError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match *self {
            WordCountError::EmptySource => None,
            WordCountError::ReadError { ref source } => Some(source),
            WordCountError::IOError(_) => None,
        }
    }
}

impl std::fmt::Display for WordCountError {
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
        match *self {
            WordCountError::EmptySource => {
                write!(f, "Source contains no data")
            }
            WordCountError::ReadError { .. } => {
                write!(f, "Read error")
            }
            WordCountError::IOError(ref err) => {
                err.fmt(f)
            }
        }
    }
}

impl From<std::io::Error> for WordCountError {
    fn from(err: std::io::Error) -> WordCountError {
        WordCountError::IOError(err)
    }
}

Comments