Should I abstract error types in my Rust API? [duplicate] - error-handling

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?

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.

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.

Is that the most idiomatic way to go about it? And how do I implement the Error trait?
It's a common way, yes. "idiomatic" depends on how strongly typed you want your errors to be, and how you want this to interoperate with other things.
And how do I implement the Error trait?
Strictly speaking, you don't need to here. You might for interoperability with other things that require Error, but since you've defined your return type as this enum directly, your code should work without it.

Related

Why does a standard error need to be boxed in main, but not an io error?

Take the following code snippet:
fn main() -> std::result::Result<(), std::io::Error> {
println!("Bonjour le Monde");
Ok(())
}
This is perfectly fine code, but I wondered what would happen if I changed the type of the error to std::error::Error:
fn main() -> std::result::Result<(), std::error::Error> {
println!("Bonjour le Monde");
Ok(())
}
This is not good anymore:
error[E0277]: the size for values of type (dyn std::error::Error + 'static) cannot be known at compilation time
I fixed it like this:
fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
println!("Bonjour le Monde");
Ok(())
}
So why does the main function require Box<> for standard errors, but not for io errors?
std::error::Error is a trait included in the standard library. As Rust points out, it cannot determine how much memory will be need to store an Error at compiled time since a structure of any size could potentially be used. As such, you need to use some form of indirection, such as a reference, a pointer, or, in this case, a Box. See TRPL Ch.17 for more information about using trait objects.
In contrast, std::io::Error is a structure from the std::io module (which also happens to implement std::error::Error). Rust does know large this structure is, so it can create a monomorphization of Result that uses it.

Can you return a Result that works with any possible error type?

I want to use multiple libraries that each have their own error types. I don't really care about each specific crate's error type and I want to use the ? idiom to use the methods of those crates that return a Result type.
I don't want to unwrap the values either, that would cause a panic if it hits an error. I might just want to propagate the different errors using ? to the top and perhaps choose to deal with them or ignore them if I want.
I cannot do that with a std::result::Result<T, E> because I don't know the type of error returned (like I said, each crate could return its own errors).
I am aware that in Rust there is no "object-oriented" polymorphism, but there are trait objects. Since a trait object's size cannot be known at compile time, we must hide them behind some kind of pointer like & or Box<_>.
The base trait implemented by errors seems to be std::error::Error.
One thing I've seen is the fn foo() -> Result<Blah, Box<dyn Error>> strategy, which utilizes the concept of trait objects.
The problem with this strategy is none of the crates return a boxed error, which leads to the compiler complaining about the same.
An example use-case:
use native_tls::TlsConnector; // 0.2.3
use std::io::{Read, Write};
use std::net::TcpStream;
fn main() {
match do_stuff() {
Ok(string) => {
println!("{}", string);
}
_ => {
println!("Failed!");
}
}
}
fn do_stuff() -> Result<String, Box<(dyn std::error::Error + 'static)>> {
let connector = TlsConnector::new()?;
let stream = TcpStream::connect("jsonplaceholder.typicode.com:443")?;
let mut stream = connector.connect("jsonplaceholder.typicode.com", stream)?;
stream.write_all(b"GET /todos/1 HTTP/1.0\r\n\r\n")?;
let mut res = vec![];
stream.read_to_end(&mut res)?;
String::from_utf8(res)
}
playground
Is there an easy way around this problem? Can I easily abstract away all the different errors and return a Result so I can use the ? idiom?
Can you return a Result that works with any possible error type?
No, you cannot. On the surface, this cannot make sense. Generic types are chosen by the caller of the function, so how would a function create an error that was chosen by someone else, without being told how to construct it?
That said, your problem is easily solved. You said:
so I can use the ? idiom
If you do that consistently, your program compiles:
let s = String::from_utf8(res)?;
Ok(s)
You could also convert the error type directly:
String::from_utf8(res).map_err(Into::into)
none of the crates return a boxed error, which leads to the compiler complaining about the same
It does not for the 5 other cases where you've used ?, so it's unclear why you make this statement.
Specifically, Box<dyn Error> can be created from any type that implements Error:
impl<'a, E: Error + 'a> From<E> for Box<dyn Error + 'a> {
fn from(err: E) -> Box<dyn Error + 'a> {
Box::new(err)
}
}
The ? operator calls From::from for you under the hood.
See also:
What is this question mark operator about?
How to manually return a Result<(), Box<dyn Error>>?
Rust proper error handling (auto convert from one error type to another with question mark)

How to implement From trait for custom error types?

I am currently trying to write a custom error type for my CLI application. Now I want to write an implementation of the From trait so my custom error type can wrap all third party library errors that can occur.
The error enum:
#[derive(Debug)] // Allow the use of "{:?}" format specifier
pub enum CustomError {
Git(git2::Error),
Other
}
Now I want to implement the From Trait for the git2::Error from the git2 library to use the ? operator in my functions.
impl From<(git2::Error)> for CustomError {
fn from(cause: git2::Error) -> Self {
CustomError::Git(cause)
}
}
But when I try to use my custom error to map an error like this:
let repo = Repository::open(path).map_err(|err| CustomError::Git)?;
I am getting the following error message:
the trait `std::convert::From<fn(git2::error::Error) -> error::CustomError {error::CustomError::Git}>` is not implemented for `error::CustomError `
Can anyone help me to understand why I am getting this error and how to solve this problem ?
Any help is appreciated
You've mixed up a whole bunch of concepts; let's see if we can walk through this together and hopefully clarify all of it.
The git2 crate has its own error type, that you no doubt have discovered. Your definition of custom errors is fine as well.
The issue is twofold:
Your implementation of From<_>
From<E> allows you to transform a type from one type to another by providing the translation function (from()).
Your implementation of this was the following:
impl From<(git2::Error)> for CustomError {
fn from(cause: git2::Error) -> Self {
CustomError::Git(cause)
}
}
Brackets in rust aren't added where they should not be, and this is precisely one of the cases where this is the case. By doing this, you've actually defined From<(T)>, not From<T>. That's mistake #1.
The correct implementation simply drops the brackets:
impl From<git2::Error> for CustomError {
fn from(cause) -> Self {
CustomError::Git(cause)
}
}
Your actual conversion
Not an error per se, but a completely unnecessary operation as the ? operator handles it for you. There is no need for the map_err(), and if there was you'd be using into() rather than hard-calling the type (which should already be defined as a type in your function).
Remember, the whole point of conversion traits is to define them so you don't have to explicitly call them.
A final "demo" version of the code in working order could look like this:
extern crate git2;
use git2::Repository;
#[derive(Debug)] // Allow the use of "{:?}" format specifier
pub enum CustomError {
Git(git2::Error),
Other
}
impl From<(git2::Error)> for CustomError {
fn from(cause: git2::Error) -> Self {
CustomError::Git(cause)
}
}
fn test() -> Result<(), CustomError> {
let path = "foo";
let output = Repository::open(path)?;
Ok(())
}
fn main() {
println!("Hello, world!");
}

How does the ? operator interact with the From trait?

Say I have the following:
use std::fs::File;
impl From<i32> for Blah {
fn from(b:i32) -> Blah {
Blah {}
}
}
fn main() {}
enum MyError {
ParseError,
}
impl From<std::io::Error> for MyError {
fn from(_:std::io::Error) -> Self {
MyError::ParseError
}
}
fn get_result() -> Result<Blah, MyError> {
let mut file = File::create("foo.txt")?;
}
This compiles fine. I don't understand how.
File::create throws an std::io::error, which we're trying to wrap in a MyError. But we never explicitly call from anywhere!? How does it compile?
As the comments from this answer Rust understanding From trait indicate, you do have to explicitly call from.
So, how is the above snippet compiling?
The difference is stated in The Rust Programming Language, chapter 9, section 2, when talking about the ? operator:
Error values that have the ? operator called on them go through the from function, defined in the From trait in the standard library, which is used to convert errors from one type into another. When the ? operator calls the from function, the error type received is converted into the error type defined in the return type of the current function. This is useful when a function returns one error type to represent all the ways a function might fail, even if parts might fail for many different reasons. As long as each error type implements the from function to define how to convert itself to the returned error type, the ? operator takes care of the conversion automatically.
You have provided this implementation of From<std::io::Error> for that error type, therefore the code will work and convert values of this type automatically.
The magic is in the ? operator.
let mut file = File::create("foo.txt")?;
expands to something like (source)
let mut file = match File::create("foo.txt") {
Ok(t) => t,
Err(e) => return Err(e.into()),
};
This uses the Into trait, which is the counterpart to the From trait: e.into() is equivalent to T::from(e). Here you have the explicit conversion.
(There is an automatic impl<T, U> Into<U> for T for every impl<T, U> From<T> for U, which is why implementing From is enough.)

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?
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.
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.
Is that the most idiomatic way to go about it? And how do I implement the Error trait?
It's a common way, yes. "idiomatic" depends on how strongly typed you want your errors to be, and how you want this to interoperate with other things.
And how do I implement the Error trait?
Strictly speaking, you don't need to here. You might for interoperability with other things that require Error, but since you've defined your return type as this enum directly, your code should work without it.