Skip to content

Instantly share code, notes, and snippets.

@aksh1618
Last active January 4, 2025 06:38
Show Gist options
  • Save aksh1618/d50295ceca7e4a3f568232c9907411bf to your computer and use it in GitHub Desktop.
Save aksh1618/d50295ceca7e4a3f568232c9907411bf to your computer and use it in GitHub Desktop.
Rust Deep Dive - Error Handling πŸ¦β€πŸ”₯

Rust Deep Dive - Error Handling πŸ¦β€πŸ”₯

Slide from a talk by Jane Losare-Lusby, who maintains the `eyre` crate

Slide from a talk by Jane Losare-Lusby, who maintains the eyre crate

  • Starting on a real world rust project, the first pattern thing-y I encountered pretty quickly was custom error handling and converting other errors into my error in functions returning a result.
  • Pre-prerequisite: Repeat 3 times, giving it more thought every time: Errors in Rust are not Exceptions!!! They're values
  • Prerequisite: Rust on its own provides 3 things for error handling
    • std::result::Result
    • std::error::Error
    • ? which is an operator that is equivalent to:
      match operand {
          Ok(x) => x,
          Err(e) => return Err(From::from(e)),
      }
      • Theoretically anything that implements the {rust} Try trait can use this operator (which is sometimes called the {rust} Try operator), but it's currently unstable, so we can only use it with {rust} Result & {rust} Option
  • There are a lot of patterns here in rust, including:
    • A custom {rust} mycrate::Result<T> type alias, such as: {rust} pub type Result<T> = std::result::Result<T, MyCrateError>; to use as return type for functions without having to repeat the error
    • Providing {rust} impl Error for custom error types
    • Providing {rust}impl <T: Error> From<T> to allow for conversion of other errors to our error type when using the ? operator (As it calls {rust} From::from in the {rust} Err match arm)
    • Using enum variants per other such error: keeping our errors as sum types.
      • In general also using enums etc. instead of strings in order to allow the user to get as much information as possible about the error.
      • Rust by example section on this: Wrapping errors - Rust By Example
  • A lot of these patterns are easily implemented using the thiserror crate by David Tolnay
    • Also consider if using derive_more crate instead is better if only {rust} From implementation is required and {rust} Display is not required, e.g.: In a backend service, the {rust} Display implementation is not likely to be used: {rust} Serialize is much more likely to be used to just print the context variables as a JSON
    • For the {rust} Display part there is also displaydoc by the maintainer of eyre: it takes the message from doc comments instead of an attribute, achieving both documentation & printability while keeping them in sync as well.
  • anyhow is another crate that acts as {rust} Box<dyn Error> (but more performant with a custom v table implementation) replacement to allow using ? with any {rust} T: Error in functions returning {rust} anyhow::Result.
    • However, it's probably not a good practice to use anyhow, as it's a way of shoving your work under the rug for later
    • Another notable crate is eyre, focusing on error reporting customizability.
    • Finally, snafu provides syntax sugar for {rust} map_err by generating a convenient context propagation syntax
  • What do I use then? Depends if it's a library or an application
    • For library errors: We need maximum flexibility here, so it's not a good idea to use error reporters such as anyhow, eyre or Box<dyn Error>, as the don't compose well due to not implementing Error itself (due to Rust's overlap rule). So use error defining traits, such as thiserror
      • Also make sure to use non-exhaustive in the Error enum being defined.
    • For application errors: Here we need reporting for arbitrary errors as they'll encounter errors from numerous sources. So anyhow, eyre etc. work well here.
    • Note that these are just rules of thumb, specific situations might warrant a different approach.
  • For any new project, add these two lines in error.rs and re-export in the beginning of main.rs/lib.rs:
    // In main.rs/lib.rs
    mod error;
    pub use self::error::{Error, Result};
    
    // In error.rs
    pub type Result<T> = core::result::Result<T, Error>;
    pub type Error = Box<dyn std::error::Error>; // For early dev
  • Eventually we want to move to something like this:
    use derive_more::From;
    use core::{fmt::{self, Display, Formatter}, result};
    
    pub type Result<T> = core::result::Result<T, Error>;
    
    #[derive(Debug, From)]
    pub enum Error {
        // -- When fs module doesn't have too many errors yet
        LimitTooHigh { actual: usize, max: usize },
    
        // -- When fs module has more than 2-3 errors
        #[from]
        Fs(fs::Error),
    
        // -- Externals
        #[from]
        Io(std::io::Error), // as an example
    }
    
    // Note: Implement Display as debug, for Web and app error, as anyway
    // those errors will need to be streamed as JSON probably to be rendered
    // for the end user. 
    impl Display for Error {
        fn fmt(&self, fmt: &mut Formatter) -> result::Result<(), fmt::Error> {
            write!(fmt, "{self:?}")
        }
    }
    
    impl std::error::Error for Error {}

Sources

  • This video gives an intuition for how to think about Error Handling: RustConf 2020 - Error handling Isn't All About Errors by Jane Lusby - YouTube
  • This video gives an idea of what creating an error involves, and what boilerplate is required: Rust - Which error-handling crate to use? ErrorTypes, thiserror & anyhow - YouTube
  • This video sums up a lot of the patterns very well: A Simpler Way to See Results - YouTube
  • This video goes over the best practices and patterns the Rust community is heading towards: Rust Error Handling - Best Practices - YouTube
  • A good article to go through before creating custom errors when creating a library crate for others to use: Study of std::io::Error
  • Other articles:

Notable Discussions

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment