Inspired by this question, I want to ask about how to handle input data type error in Rust. For example, the following function require input data type to be enum Animal. How about the user actually give an input with not-defined data type or even a empty one.
Should I add a None => None or _ => None in the match?
use std::fmt;
use std::io::prelude::*;
pub enum Animal {
Cat(String),
Dog,
}
impl fmt::Display for Animal {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
Animal::Cat(ref c) => f.write_str("c"),
Animal::Dog => f.write_str("d"),
}
}
}
fn main() {
let p: Animal = Animal::Cat("whiskers".to_owned()); // yes, work!
let p: Animal = Animal::BadCat("whiskers".to_owned()); // Badcat not defined,??
}
Compiler error:
error: no associated item named `BadCat` found for type `Animal` in the current scope
--> <anon>:20:25
|
20 | let p: Animal = Animal::BadCat("whiskers".to_owned()); // Badcat not defined,??
| ^^^^^^^^^^^^^^
Rust is a strongly1, statically typed, compile-time type checked language.
This means that unlike, say, Python, it is impossible for a user to pass an invalid type to your function (unless they're passing data that has been invalidly coerced via unsafe, but you can't reasonably detect this. See my post about incorrect transmutations to bool). You do not need to worry about this.
In the more general case, the only time you need to worry about type variant checking in Rust is when receiving data from outside Rust, e.g. through a configuration or data file, or else an FFI function. In these cases, it's customary to return a Result<Animal,ErrorMessage> of some sort to indicate bad data. However, in some cases, especially in FFI, if you receive data that's particularly malformed it may be acceptable to panic!.
This type of checking usually does not involve match statements on an enum, but rather more fundamental checks such as assertions about string or integer comparisons being one of a set of known values that you're trying to reinterpret to a higher level enum type.
1 There's some inconsistency and disagreement on what "strongly typed" actually means, in this case I'm using it to mean "few if any implicit type coercions".
Related
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.
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)
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.)
Given this crate referencing an error from another crate, I would normally write a From implementation to convert types.
use xladd::variant::{Variant, XLAddError};
use failure::Fail;
use std::convert::TryInto;
use std::convert::From;
use std::error::Error;
#[derive(Debug, Fail)]
enum AARCError {
#[fail(display = "F64 Conversion failure")]
ExcelF64ConversionError,
#[fail(display = "Bool Conversion failure")]
ExcelBoolConversionError,
#[fail(display = "Conversion failure")]
ExcelStrConversionError,
}
impl From<XLAddError> for AARCError {
fn from(err: XLAddError) -> Self {
AARCError::ExcelF64ConversionError // Test for now
}
}
pub fn normalize(array: Variant, min: Variant, max: Variant, scale: Variant) -> Result<Variant, AARCError> {
let min: f64 = min.try_into().map_err(|e| AARCError::from(e))?;
Ok(Variant::from_str("foo"))
}
But in this case I get an error:
error[E0277]: the trait bound `basic_stats::AARCError: std::convert::From<!>` is not satisfied
--> src\basic_stats.rs:24:48
|
24 | let min: f64 = min.try_into().map_err(|e| AARCError::from(e))?;
| ^^^^^^^^^^^^^^^ the trait `std::convert::From<!>` is not implemented for `basic_stats::AARCError`
|
= help: the following implementations were found:
<basic_stats::AARCError as std::convert::From<xladd::variant::XLAddError>>
= note: required by `std::convert::From::from`
I don't understand what the From<!> trait is and trying to implement something like that gives an error for unnamed types.
What should I be doing to enable Rust to convert the external crate's errors to my ones?
I don't understand what the From<!> trait is and trying to implement something like that gives an error for unnamed types.
! is the "never" or uninhabited type; the type that has no possible values.
If a Result has ! for its error type, that means the operation cannot fail. It is impossible to convert from it to some other error type, because an error value cannot exist in the first place.
The never type is currently an experimental feature, requiring a nightly build of Rust. As such, it likely has a few rough edges, and it isn't as ergonomic as it could be. For example, I would expect the final feature to provide a blanket From<T> implementation for all types that implement TryFrom<T> with associated type Error = !. It should be made easy to not have to handle the error that can't happen.
To fix your immediate problem, you can map that error to unreachable!(). The only issue with that approach is forwards-compatibility - if the third party crate later introduces a reachable error then your code would have an unhandled error, and no compile-time error to protect you. That's probably a part of why ! is not yet stabilised.
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.