Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do you define custom `Error` types in Rust?

I'm writing a function that could return several one of several different errors.

fn foo(...) -> Result<..., MyError> {} 

I'll probably need to define my own error type to represent such errors. I'm presuming it would be an enum of possible errors, with some of the enum variants having diagnostic data attached to them:

enum MyError {     GizmoError,     WidgetNotFoundError(widget_name: String) } 

Is that the most idiomatic way to go about it? And how do I implement the Error trait?

like image 868
Jo Liss Avatar asked Mar 03 '17 16:03

Jo Liss


People also ask

What is a custom error?

The CustomError class provides a way to programmatically access and modify the error section of a configuration file. This type is part of a group that includes the CustomErrorCollection, CustomErrorsMode, and CustomErrorsSection.

What does OK () do in Rust?

Result<T, E> is the type used for returning and propagating errors. It is an enum with the variants, Ok(T) , representing success and containing a value, and Err(E) , representing error and containing an error value. Functions return Result whenever errors are expected and recoverable.

How do you throw an exception in Rust?

Rust doesn't have exceptions. Instead, it has the type Result<T, E> for recoverable errors and the panic! macro that stops execution when the program encounters an unrecoverable error.


2 Answers

You implement Error exactly like you would any other trait; there's nothing extremely special about it:

pub trait Error: Debug + Display {     fn description(&self) -> &str { /* ... */ }     fn cause(&self) -> Option<&Error> { /* ... */ }     fn source(&self) -> Option<&(Error + 'static)> { /* ... */ } } 

description, cause, and source all have default implementations1, and your type must also implement Debug and Display, as they are supertraits.

use std::{error::Error, fmt};  #[derive(Debug)] struct Thing;  impl Error for Thing {}  impl fmt::Display for Thing {     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {         write!(f, "Oh no, something bad went down")     } } 

Of course, what Thing contains, and thus the implementations of the methods, is highly dependent on what kind of errors you wish to have. Perhaps you want to include a filename in there, or maybe an integer of some kind. Perhaps you want to have an enum instead of a struct to represent multiple types of errors.

If you end up wrapping existing errors, then I'd recommend implementing From to convert between those errors and your error. That allows you to use try! and ? and have a pretty ergonomic solution.

Is that the most idiomatic way to go about it?

Idiomatically, I'd say that a library will have a small (maybe 1-3) number of primary error types that are exposed. These are likely to be enumerations of other error types. This allows consumers of your crate to not deal with an explosion of types. Of course, this depends on your API and whether it makes sense to lump some errors together or not.

Another thing to note is that when you choose to embed data in the error, that can have wide-reaching consequences. For example, the standard library doesn't include a filename in file-related errors. Doing so would add overhead to every file error. The caller of the method usually has the relevant context and can decide if that context needs to be added to the error or not.


I'd recommend doing this by hand a few times to see how all the pieces go together. Once you have that, you will grow tired of doing it manually. Then you can check out crates which provide macros to reduce the boilerplate:

  • error-chain
  • failure
  • quick-error
  • Anyhow
  • SNAFU

My preferred library is SNAFU (because I wrote it), so here's an example of using that with your original error type:

use snafu::prelude::*; // 0.7.0  #[derive(Debug, Snafu)] enum MyError {     #[snafu(display("Refrob the Gizmo"))]     Gizmo,     #[snafu(display("The widget '{widget_name}' could not be found"))]     WidgetNotFound { widget_name: String }, }  fn foo() -> Result<(), MyError> {     WidgetNotFoundSnafu {         widget_name: "Quux",     }     .fail() }  fn main() {     if let Err(e) = foo() {         println!("{}", e);         // The widget 'Quux' could not be found     } }  

Note I've removed the redundant Error suffix on each enum variant. It's also common to just call the type Error and allow the consumer to prefix the type (mycrate::Error) or rename it on import (use mycrate::Error as FooError).


1 Before RFC 2504 was implemented, description was a required method.

like image 153
Shepmaster Avatar answered Oct 11 '22 02:10

Shepmaster


The crate custom_error allows the definition of custom error types with less boilerplate than what was proposed above:

custom_error!{MyError      Io{source: io::Error}             = "input/output error",      WidgetNotFoundError{name: String} = "could not find widget '{name}'",      GizmoError                        = "A gizmo error occurred!" } 

Disclaimer: I am the author of this crate.

like image 37
lovasoa Avatar answered Oct 11 '22 02:10

lovasoa