rust-lang-deprecated / failure Goto Github PK
View Code? Open in Web Editor NEWError management
Home Page: https://rust-lang-nursery.github.io/failure/
License: Apache License 2.0
Error management
Home Page: https://rust-lang-nursery.github.io/failure/
License: Apache License 2.0
Error chain's chain_err
takes a Fn
so it will only be evaluated in the error case.
fn process_collection() -> Result<()> {
let path = "non-existent-file";
process_file(path).chain_err(|| format!("Processing {}", path))
}
Unless I've misread things, Context
can't. It takes a Display
fn process_collection() -> Result<(), Error> {
let path = "non-existent-file";
process_file(path).context(format!("Processing {}", path))?;
Ok(())
}
Another use case where we might want for it to be lazy: .clone()
-ing the object passed to it.
I like the general design of this crate a lot. I think that Fail is a solid improvement over the existing Error trait, and that the proposed Error struct is a good default mechanism for error handling (though not a universal approach, since it relies on dynamic memory allocation, we'll probably continue to need mechanisms for generating a One True Error Type for some use cases).
That being said, I would prefer for my error handling code not to be riddled with internet meme references, so it would be nice if there were a more "english" name like Failure for the trait that error types need to implement.
And on a related note, there is the question of whether we could find less synonymous names than "Error" and "Fail(ure)" in order to more easily discriminate which of the two is a trait and which is a concrete type. This is especially true since if this crate gets popular, we would enter a transition period where people's crates manipulate two kinds of Error, one of which is a trait (std::error::Error) and one of which is a struct (failure::Error). This could get confusing quickly.
With these two considerations in mind, how about naming the trait Error and the struct AnyError? Error is consistent with the std naming convention, and allows for a smooth migration if the trait is eventually merged into the standard library, whereas AnyError should hint quite well at the fact this this is a type that can hold any error (with a subtle hint at the Any trait for type erasure).
ResultExt
provides both a context(d)
and with_context(FnOnce(...) -> D)
, allowing for lazily evaluated contexts.
Error
includes support for context(d)
but not with_context
.
Moving this from reddit.
Context
can either be a cheaply constructed Fail
or an extension to Error
. It doesn't allow wrapping a struct
that implements Fail
.
This was more understandable when it felt like the focus was exclusively on using Error
but now with the emphasis being on there being multiple workflows, this seems like shortcoming in the API.
For example, it can be very hard for someone to understand the meaning of context
when reading the following line (specially a person not very familiar with the failure crate):
let mut file = File::open(cargo_toml_path).context("Missing Cargo.toml")?;
Something like fail_context
would probably be more appropriate and communicate the intent better.
It could be great to use a decorator to automatically add one or more fields when converting an error to another error. Perhaps it could look like this
#[derive(Fail, Debug)]
struct ParseError {
filename: String,
error: MyError
}
// do_stuff returns a MyError, but the add_fields decorator causes the error to be
// converted in to a ParseError adding a filename.to_owned() as a field.
#[fail(add_fields = [filename])]
fn read_file(filename: &str) -> Result<(), ParseError>{
do_stuff(filename)?;
do_stuff(filename)?;
Ok(())
}
I am not sure if the above is actually doable, and the syntax probably needs tweaking, but it could be really useful to have something to this effect.
Hi, I scoured the docs and had a bit of a hard time discerning if Failure should handle the NoneError
type that can be returned when a you call to_str()
on a PathBuf
, I'm migrating my code from error_chain
and I used the .chain_error()
method to convert these errors into compatible types. Am I missing something or should I implement From
fro NoneError
?
Currently there is cause
and root_cause
.
It'd be nice to have something like iter_causes
to help in cases like this
println!("Error: {}", err);
let mut cause = err.cause();
while let Some(c) = cause {
println!("Caused by: {}", c);
cause = c.cause();
}
to
println!("Error: {}", err);
for cause in err.iter_causes() {
println!("Caused by: {}", cause);
}
There's a trick that can export traits and proc macros from one crate. Using this would make the dependency declaration cleaner, and could be gated on a default feature for people who don't need the macros.
Maybe its from having come from error-chain where too much magic is happening but I feel the documentation obscures the fact that custom Fail
s will not have a backtrace
or cause
.
Two simple ways to improve this
backtrace
" and "Overriding cause
" to "Including a backtrace
" and "Including a cause
".
Please take a look at example code. I have no idea why it is not possible to implement my trait for failure::Error
and for T where T: Fail
. As far as I saw the source, failure::Error
does not implement Fail
.
Code sample with error:
#[macro_use]
extern crate failure_derive;
trait FailMethods {
fn foo(&self);
}
impl FailMethods for ::failure::Fail {
fn foo(&self) {
println!("foo");
}
}
impl<T> FailMethods for T where T: ::failure::Fail {
fn foo(&self) {
(self as &::failure::Fail).foo();
}
}
// impl FailMethods for ::failure::Error {
// fn foo(&self) {
// self.cause().foo();
// }
// }
/*
error[E0119]: conflicting implementations of trait `FailMethods` for type `failure::Error`:
--> src/main.rs:21:1
|
15 | / impl<T> FailMethods for T where T: ::failure::Fail {
16 | | fn foo(&self) {
17 | | (self as &::failure::Fail).foo();
18 | | }
19 | | }
| |_- first implementation here
20 |
21 | / impl FailMethods for ::failure::Error {
22 | | fn foo(&self) {
23 | | self.cause().foo();
24 | | }
25 | | }
| |_^ conflicting implementation for `failure::Error`
error: aborting due to previous error
*/
#[derive(Debug, Fail)]
#[fail(display = "my error")]
struct MyError {}
fn main() {
let failure_error = ::failure::err_msg("dummy error");
let custom_derived = MyError {};
custom_derived.foo();
failure_error.cause().foo();
//failure_error.foo();
/* error[E0599]: no method named `foo` found for type `failure::Error` in the current scope
--> src/main.rs:37:17
|
37 | failure_error.foo();
| ^^^
|
= note: the method `foo` exists but the following trait bounds were not satisfied:
`failure::Error : failure::Fail`
= help: items from traits can only be used if the trait is implemented and in scope
= note: the following trait defines an item `foo`, perhaps you need to implement it:
candidate #1: `FailMethods`
error: aborting due to previous error
*/
}
If context accepts a generic displayable thing as a context I see no inherent cost at adding another generic for error too, i.e. Context<D: Display, E: Fail>
. Or is it somehow related to how question-mark/carrier work?
Should libraries have 1 error type in their API, or many? error-chain today strongly encourages having a single error type, which is probably not always the best approach.
The primary reason to have multiple error types is that you can then easily restrict a function to throwing only 1 particular error type. The primary disadvantage is that it becomes harder to deal with cases where there are multiple error types.
For example, in the error-chain docs, an example is shown with four variants:
Notably, when parsing a toolchain identifier from a string, only the first two variants could occur. It would make sense, then, to have a situation like this:
#[derive(Debug, Fail)]
enum ToolchainError {
InvalidName,
InvalidVersion,
}
impl FromStr for ToolchainId {
type Err = ToolchainError;
}
But this raises the question: should the larger section of code, which reads/parses a file containing toolchain ids in a toml document, have its own new error type, adding the other variants, or should it just return Error
?
The questions posed then are:
Error
type?I am currently writing a parser for fun, and I decided to give the failure crate a go.
In my attempts to try and provide as much information as possible in errors, I want to pass the offending token in my struct, and get information out of that token.
Here is the offending code:
#[derive(Debug, Fail)]
pub enum ParseError {
#[fail(display = "Expecting a token to exist, but no token was found.")]
NoTokenFound,
#[fail(display = "{}: Expected a token of type {:?}, but got a token of type {:?}.", location,
expected, got.ttype)]
UnexpectedToken {
location: Location,
got: Token,
expected: TokenType,
},
}
When I change the reference to got.ttype to simply "got", it works fine.
Could we enable accessing struct fields, or is there a reason we must avoid using it?
I've noticed that the documentations for this crate uses the convention `Fail`ure
in place of the word failure
. I'm finding this a bit hard to read, so I was wondering if you'd accept a PR to change it to just failure
in the appropriate places.
This is probably a simple issue, but I could not figure it out and I suppose others will also stumble upon it. It would be good if you could include a working example in the documentation for the situation described below:
I have my own error type which derives from Fail
. It is unclear to me how it can be automatically converted to a failure::Error
.
Minimal example:
Cargo.toml:
[dependencies]
failure = "0.1.0"
failure_derive = "0.1.0"
lib.rs:
extern crate failure;
#[macro_use] extern crate failure_derive;
use failure::{Error};
#[derive(Debug,Fail)]
#[fail(display = "This is my error and mine alone!")]
pub struct MyError;
pub struct Foo;
fn foo() -> Result<Foo,Error> {
Err( MyError{} )
}
The error I receive when I am compiling this example:
error[E0308]: mismatched types
--> src/lib.rs:15:10
|
15 | Err( MyError{} )
| ^^^^^^^^^ expected struct `failure::Error`, found struct `MyError`
|
= note: expected type `failure::Error`
found type `MyError`
Thank you so much for this crate! This is already a lot nicer than error-chain.
This is the convention for such methods in the standard library (BufReader::into_inner
) and widely used crates (flate2::read::GzEncoder::into_inner
).
#[derive(Fail)]
enum MyError {
#[fail(display = "The unthinkable happened: {}", 0)]
SomeProblem(SomeData);
}
It's not clear how to provide a fail(display) attribute for this kind of error. Trying the above, I get an error that non-string literals in attributes are experimental (issue #34981).
It would be nice to be able to return early from a function using macros such as the bail! and ensure! macro of error-chain.
It would be great to add to .travis.yml an additional test run which does cargo test --no-default-features
(turning off the std
feature) on stable. The 4 existing test runs should still use std.
Not sure how to do this, but I'm sure its possible if someone reads the Travis docs. :)
#[derive(Debug)]
struct MyError {
inner: Context<MyErrorKind>,
}
#[derive(Copy, Clone, Eq, PartialEq, Debug, Fail)]
struct MyErrorKind {
// A plain enum with no data in any of its variants
//
// For example:
#[fail(display = "A contextual error message.")]
OneVariant,
// ...
}
struct MyErrorKind
should be enum MyErrorKind
.
I have been toying around with rust error handling for a long time now and I love where this crate here is going, but it misses out on one part that keeps frustrating me a bit.
There is no good way to identify a unique error unless you have the type. This makes sense but is very restrictive. What if instead an failure would have a UUID. The default can be generated from an md5 hash of the fully qualified name (UUID3) but alternatively users can manually implement it.
This can then be used for localization easily (#12):
impl Fail for Whatever {
const ID: [u8; 16] = b"!\x06?"\xe6\x12F\x0f\x92t\xfe\xaa\x93\xf9<L";
}
The localization can then key the strings to that ID. Likewise this lets you send failures over the wire easily.
To make the error formatting better the messages however would have to be split up into more parts. In particular I am thinking of:
Display
maybe?)The idea would be that name + description are reasonably static strings that can be looked up from a translation table.
I just finished reading the book. I liked the "Patterns & Guidance" section, but something is still a bit unclear to me.
In the library I'm currently writing, I'd like to use a custom error enum containing all possible error cases. When using error_chain, I added a msg: String
field as data to almost all the variants, in order to be able to add some context. For this library, this would translate to
#[derive(Fail, Debug)]
pub enum MyError {
#[fail(display = "Parsing error: {}", _0)]
Parsing(String),
#[fail(display = "Overflow: {}", _0)]
Overflow(String),
}
// ...
return Err(MyError::Parsing(format!("Could not parse URL \"{}\", url)));
This way I can simply return a Result<_, MyError>
from my public library functions.
From what I understood when reading the failure
docs, the recommended way to deal with this is the context though. So I tried to adapt the enum:
#[derive(Fail, Debug)]
pub enum MyError {
#[fail(display = "Parsing error")]
Parsing,
#[fail(display = "Overflow")]
Overflow,
}
// ...
return Err(MyError::Parsing).context(format!("Could not parse URL \"{}\", url)));
Now the return type of my functions can't be Result<_, MyError>
though anymore, since the .context()
method converts the error type to a Context
. This means that I'd have to return Result<_, failure::Error>
instead, right? That prevents the user from easily enumerating the errors though.
What is the suggested pattern in this case? Going back to the "embedded" context in the error types?
I definitely just threw together the Debug and Display impls in this library for types like Error, Context, etc. They should be reviewed for quality and better ways of conveying the same information should be considered and discussed.
Thank you for digging into this tricky design problem!
I miss the "chained" error display provided by quick_main!
in error-chain
. A lot of my software has been designed around showing the various nested error messages. For example:
error reading "avatar_01_01.es.srt2": No such file or directory (os error 2)
Showing the full context of the error provides helpful clues to the user. But failure
provides no easy way to get that string, and it seems to encourage the use of errors like:
error reading "avatar_01_01.es.srt2"
This is less ergonomic for users.
One workaround is to write something like this:
// Decide which command to run, and run it, and print any errors.
if let Err(err) = run(&args) {
let mut opt_cause = Some(err.cause());
let mut first = true;
while let Some(cause) = opt_cause {
if first {
first = false;
} else {
write!(io::stderr(), ": ")
.expect("unable to write error to stderr");
}
write!(io::stderr(), "{}", cause)
.expect("unable to write error to stderr");
opt_cause = cause.cause();
}
write!(io::stderr(), "\n").expect("unable to write error to stderr");
process::exit(1);
}
This is potentially related to #41.
I don't think it's necessary to go as far as quick_main!
. It might be enough to have a display_with_causes
method or even a DisplayWithCauses
newtype, allowing us to write something like:
write!(io::stderr(), "{}\n", err.display_with_causes())
Obviously some of this could be provided by an external crate. But there might be some nice ergonomic improvements to be made here.
Sorry if this is addressed but I didn’t immediately see how From<SomeError>
works for an error that wraps another error, e.g. io::Error
Are we currently expected to implement from conversion for error types wrapping other errors ourselves?
E.g., this fails to compile:
#[derive(Fail, Debug)]
pub enum LinkError<'a> {
#[fail(display = "Undeclared symbolic reference to: {}", _0)]
Undeclared (&'a str),
}
Are there plans to ever support this, or is this not possible due to other constraints?
In some ways, an Error
acts as a proxy for its inner
Fail
. In other ways, it doesn't. This seems like it could lead to surprising results when swapping one out for the other.
Fail
Error
In comparing them, the discrepancy is in cause
.
I noticed this when reading the documentation for Error::causes
.
extern crate failure;
fn main() {
println!("{}", std::mem::size_of::<failure::Error>());
}
Currently 2 words but I would expect this to be 1 word. In my experience a return type like Result<(), Error>
can be faster if 1 word than 2 words. (Let me know if this is controversial and I can try to recreate some benchmarks.)
Would it be possible to double-Box or use a C++like layout for the DST so Error can be a thin pointer?
In the code example in README.md:
// All we do is derive Fail and Display for it, no other "magic."
#[derive(Debug, Fail)]
enum ToolchainError {
Should the comment say "Fail and Debug" or should it be #[derive(Display, Fail)]
?
I'm converting the next
branch of https://github.com/faradayio/bigml-rs from error-chain
to failure
, and I ran into an interesting lifetime issue using with_context
.
In practice, this library really needs to return detailed context for errors: a message like "404 Not Found" provides totally insufficient debugging help, and we have a policy of wrapping these errors in a context containing the URL that failed.
But since catch
isn't available on stable Rust, we tend to do this by declaring a local mkerr
closure, and passing it to chain_err
like chain_err(&mkerr)
. Below, I've tried to convert this code to work with failure
:
/// Create a new resource.
pub fn create<Args>(&self, args: &Args) -> Result<Args::Resource>
where Args: resource::Args
{
let url = self.url(Args::Resource::create_path());
debug!("POST {} {:#?}", Args::Resource::create_path(), &serde_json::to_string(args));
let mkerr = |_| CouldNotAccessUrl::new(&url);
let client = reqwest::Client::new();
let res = client.post(url.clone())
.json(args)
.send()
.with_context(&mkerr)?;
Ok(self.handle_response(res).with_context(&mkerr)?)
}
This gives me the compiler error:
error[E0271]: type mismatch resolving `for<'r> <&[closure@src/client.rs:61:21: 61:53 url:_] as std::ops::FnOnce<(&'r reqwest::Error,)>>::Output == _`
--> src/client.rs:66:14
|
66 | .with_context(&mkerr)?;
| ^^^^^^^^^^^^ expected bound lifetime parameter, found concrete lifetime
|
= note: concrete lifetime that was found is lifetime '_#131r
error[E0281]: type mismatch: `[closure@src/client.rs:61:21: 61:53 url:_]` implements the trait `std::ops::Fn<(_,)>`, but the trait `for<'r> std::ops::Fn<(&'r reqwest::Error,)>` is required
--> src/client.rs:66:14
|
61 | let mkerr = |_| CouldNotAccessUrl::new(&url);
| -------------------------------- implements `std::ops::Fn<(_,)>`
...
66 | .with_context(&mkerr)?;
| ^^^^^^^^^^^^
| |
| expected concrete lifetime, found bound lifetime parameter
| requires `for<'r> std::ops::Fn<(&'r reqwest::Error,)>`
|
= note: required because of the requirements on the impl of `for<'r> std::ops::FnOnce<(&'r reqwest::Error,)>` for `&[closure@src/client.rs:61:21: 61:53 url:_]`
Now, I can work around this trivially. Among many other solutions, I can just write:
.with_context(|_| CouldNotAccessUrl::new(&url))?
...which works perfectly fine. Indeed, if catch
were stable, this would be 100% acceptable. But given the current constraints, I wonder if there isn't some tweak to the type of with_context
that might allow my code to work?
The ToC on the left is fine, but on top-level pages that link to other sub-pages, there are links that end in .md which should end in .html. e.g.:
I am personally trying out now a light variant of the custom Error + ErrorKind approach, where I have an ErrorKind but instead of a custom Error I just use type MyError = Context<MyErrorKind>;
.
This works great for the usual from impl's but it doesn't help when I want to return try!(Err(ErrorKind::SomeKind))
directly.
Therefore I think it would be great to always have From<Inner>
implemented for Context<Inner>
. Right now I have to do try!(Err(Context::new(ErrorKind::SomeKind)))
instead, or impl the from myself, but neither are nice things to do :).
Using the currently (but probably not for very much longer) unstable Try
trait, one can use the question mark operator on Option<T>
types, coercing them into a Result<T, NoneError>
. Unfortunately, NoneError
does not impl std::error::Error
, meaning the Fail
is not implemented for NoneError
(and it being an external type, we can't implement it in our own crates).
I think this would be particularly useful in light of the pattern described in “Using the Error
type” — throwing a NoneError
fits right into the kind of rapid prototyping, "just show the user the error and exit" limited error handling that that exemplifies.
Would a PR with this change go amiss?
For "leaf" libraries and "root" applications the story from failure is pretty clear - leaf libraries should define new types that implement Fail
, and applications should use the Error
type (unless they are no_std applications).
But what about the in-between space? What if you depend on a bunch of libraries with their own error types, and introduce a few of your own as well?
There are a few options I know about:
Error
. In many cases, this is very convenient. The downsides are that you require the Error
type (making it inappropriate to call these functions when failure could be in the hot path) and that users get no additional context about what the error means in the context of your library - libfoobar threw an io::Error
, what does that mean??
conversion), and when users are trying to downcast they now have to deal with all of this additional context information.As an example of how to use Context<D>
from failure to do the contextual form:
#[derive(Debug, Fail)]
#[fail(display = "{}", inner)]
pub struct Error {
inner: Context<ErrorKind>,
}
#[derive(Debug, Fail)]
enum ErrorKind {
#[fail(display = "invalid Cargo.toml")]
InvalidCargoToml,
#[fail(display = "corrupted registry index for registry {}", _0)]
InvalidRegistryIndex(String),
#[fail(display = "network error")]
NetworkError,
}
You can now use the .context()
method to add an ErrorKind
to an underlying error like an io::Error, a parsing Error, or an error in git.
I think 1 and 2 are both broadly applicable depending on the use case, whereas 3 (what error-chain encourages today) is rarely a good idea (basically, only if you can't afford to do the allocation in Error
). But when do we recommend 1 over 2, or vice versa? How do users know whether they should introduce a contextual construct?
I copy/pasted the example code to lib.rs
, added the dependencies and ran cargo build
. This is what I got:
# cargo build
Updating registry `https://github.com/rust-lang/crates.io-index`
Compiling toml v0.4.5
Compiling test_failure v0.1.0 (file:///home/garrett/notes/rust/test_failure)
error[E0407]: method `cause` is not a member of trait `failure::Fail`
--> src/lib.rs:18:17
|
18 | #[derive(Debug, Fail)]
| ^^^^ not a member of trait `failure::Fail`
error[E0407]: method `backtrace` is not a member of trait `failure::Fail`
--> src/lib.rs:18:17
|
18 | #[derive(Debug, Fail)]
| ^^^^ not a member of trait `failure::Fail`
error[E0412]: cannot find type `Backtrace` in module `failure`
--> src/lib.rs:18:17
|
18 | #[derive(Debug, Fail)]
| ^^^^ not found in `failure`
warning: unused `#[macro_use]` import
--> src/lib.rs:6:1
|
6 | #[macro_use] extern crate serde_derive;
| ^^^^^^^^^^^^
|
= note: #[warn(unused_imports)] on by default
error[E0046]: not all trait items implemented, missing: `fail`
--> src/lib.rs:18:20
|
1 | | extern crate failure;
| |____^ missing `fail` in implementation
...
18| #[derive(Debug, Fail)]
| ____________________^
|
= note: `fail` from trait: `fn(&Self, &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error>`
error: aborting due to 4 previous errors
error: Could not compile `test_failure`.
To learn more, run the command again with --verbose.
# rustc --version
rustc 1.21.0 (3b72af97e 2017-10-09)
# cat Cargo.toml
[package]
authors = ["Garrett Berg <[email protected]>"]
name = "test_failure"
version = "0.1.0"
[dependencies]
failure = "0.0.1"
failure_derive = "0.0.1"
serde = "1.0.20"
serde_derive = "1.0.20"
toml = "0.4.5"
Let's say I'm doing some IO in a function, where several things can all fail with a std::io::Error
. I want to include some message with that error. What's the best way to do this with failure
? It looks like a custom error enum is a good way to start, but I'm unsure how to include the underlying io::Error
as the underlying cause.
#[derive(Debug, Fail)]
enum MyCustomError {
#[fail(display = "failed to read username file")]
UsernameError,
#[fail(display = "failed to read password file")]
PasswordError
}
fn run() -> Result<(), MyCustomError> {
// somehow put this `io::Error` into a MyCustomError::UsernameError
let u : File = File::open(&username_file)?;
// somehow put this `io::Error` into a MyCustomError::PasswordError
let p : File = File::open(&password_file)?;
Ok(())
}
In the talk given on Nov. 2 2017 in SF, an audience member asked about "localization". I'm not sure what specific localization techniques were being referred to in the discussion (they were never specified), but I have been using the technique of putting all string resources (for a particular locale) into a (build-time-interchangeable) module.
I merely refer to a constant (const _ &'static str
) by name wherever it is needed.
Unfortunately, referring to a const
in a Failure
display attribute does not appear to be supported:
OK:
#[derive(Debug, Fail)]
enum ErrorKind {
#[fail(display = "argument validation error")]
...
Error (error: proc-macro derive panicked):
// in en_us constants module
pub const MSG_ERR_ARG_VALIDATION: &'static str = "argument validation error";
// in error module
#[derive(Debug, Fail)]
enum ErrorKind {
#[fail(display = MSG_ERR_ARG_VALIDATION)]
...
While I understand that rich evaluations like method calls in attributes are not supported by Rust today, given how attributes are processed, can this be remedied for constants?
Yes, this is a copy of rust-lang-deprecated/error-chain#1. I'd really like to find a nice solution to this.
I've recently run into a situation where I have a function which tries multiple subroutines returning Result<_, impl Failure>
in order to produce its own output. But if all subroutines fail, it should return a descriptive failure which contains the cause, which in this case is that multiple other failures were produced.
Representing this well would require some sort of Vec<impl Failure>
in a failure. However, because cause()
and backtrace()
are biased towards the single parent case, which is the overwhelming default behavior of error chaining, this is not a simple thing to express.
Needs CI
Can Failure
introduce a consistent interface that implements PartialEq
across all Fail
types so errors can be compared for equality simply and literally? This will improve the ergonomics and productivity around unit testing, and possibly other scenarios as well.
As a BDD developer, the first thing I do when developing new code is to write a unit_test. Rust's lack of consistent API around errors makes writing unit tests less ergonomic than perhaps they should be. Here's a simple example of what I would like to write (which works, but only for some error types(!)):
#[test]
fn passing_bad_param_results_in_not_found_error() {
assert_eq!(some_func(bad_param).err().kind(), std::io::ErrorKind::NotFound);
}
I've run into what I consider to be major ergonomic issues with errors and unit testing. I haven't found a consistent way to work with errors that doesn't involve a lot of pattern-matching boilerplate. Here are some details adapted from a posting I made about a month ago:
(inconsistencies or issues in bold)
e.g. std::io::Error
:
e.g. std::fmt::Error
:
e.g. std::io::CharsError
std::error::Error
error-chain
-created errors
Error
s adds boilerplate, obfuscates code and is an incomplete solution.For example:
fn string_validator_ref_handles_empty_input() {
// GIVEN an empty string
let input = String::new();
let expected_result = ErrorKind::ValueNone;
// WHEN some operation which generates an error occurs
let result = (&input).validate_ref::<NonEmptyStringValidator>().err().unwrap()
/* What I would *like* to be able to write */
// THEN ensure the expected error was returned
assert_eq!(*result, expected_result);
}
/* The reality */
// THEN ensure the expected error was returned
assert!(matches!(*result, expected_result); //Error: pattern unreachable (match doesn't work this way; unintuitive at this level)
assert!(matches!(*result, val if val == expected_result)); //not ergonomic; Error without PartialEq (e.g. error-chain): binary operation `==` cannot be applied to type `error::ErrorKind`
assert!(matches!(*result, ErrorKind::ValueNone); //works, but without variable support, important scenarios become unrepresentable:
// suppose two methods must yield same result. To ensure consistency through refactorings, a test is developed:
// assert_eq!(matches(FuncA(badInput), FuncB(badInput)) //not possible, as per above; only constant variants w/PartialEq implemented can be used ergonomically
When considering Error equality, I believe it is out of scope for to consider the semantics of errors (e.g. "Are two instances of an error's Other
variant equal?" Assuming the two instances of Other
are value-equal then, yes. The fact that Other
could be used by a library or application as a 'catch-all' to represent a wide variety of different error conditions in the real world is out of scope for this proposal. It is expected that the user of that error type will understand when a comparison of Other
is and is not a meaningful operation).
Given that the Error
type is freqently implemented using different types (marker structs, encapsulating structs, enums, and others) the only dependencies we should take are on its API. To that end, I would love to see a consistent .kind()
method (or something else which serves the purpose of providing the error variant) which exists and is properly defined for every Fail
ure.
The crate's readme says the crate intends to replace error management in Rust, taking lessons from problems with quick-error and error-chain.
It is intended to replace error management based on std::error::Error with a new system based on lessons learned over the past several years, including those learned from experience with quick-error and error-chain.
Could someone clarify what these problems are with the aforementioned crates, and how this crate aims to solve them?
extern crate failure;
#[macro_use]
extern crate failure_derive;
#[derive(Fail, Debug)]
// ^^^^ error[E0277]: the trait bound `Never: std::fmt::Display` is not satisfied
pub enum Never { }
--> lib.rs:17:10
|
17 | #[derive(Fail, Debug)]
| ^^^^ `Never` cannot be formatted with the default formatter; try using `:?` instead if you are using a format string
|
= help: the trait `std::fmt::Display` is not implemented for `Never`
(workaround in the interim)
extern crate failure;
#[macro_use]
extern crate failure_derive;
#[derive(Fail, Debug)]
pub enum Never {
#[fail(display="wanna")]
Let(You),
}
#[derive(Debug)] pub struct You(Go);
#[derive(Debug)] pub enum Go { }
In the page, "The Fail
trait", the following sentence seems to be unfinished:
It has both a backtrace and a cause method, allowing users to get
The Fail
trait currently has a hidden __private_get_type_id__()
method which gets the TypeId
of a type to support downcasting. Would it be better to explicitly add Any
as a trait bound to Fail
instead of the hidden method?
pub trait Fail: Display + Debug + Send + Sync + 'static {
...
#[doc(hidden)]
fn __private_get_type_id__(&self) -> TypeId {
TypeId::of::<Self>()
}
If all Fail
s implement Any
that also means we'll be able to avoid the pointer casting being done in Fail::downcast_ref()
and friends and leave the responsibility of ensuring correctness to the standard library.
I'm sure you've considered this in the past seeing as the implementation of Fail::downcast_ref()
is almost identical to the one for Any
, so what was the justification for adding a #[doc(hidden)]
function for allowing downcasting?
It would be nice if there was some standard way of printing/formatting an Error or something implementing Fail.
Something that takes a &Write, maybe with a convenience helper that takes stderr/stdout, and prints the error itself, its description, its backtrace, and does the same for all the causes of it. Maybe parts of this should be configurable (don't print backtraces, don't recurse into causes).
Is this a likely pattern that we'd want to automate, or is it discouraged?
if let Err(e) = try_to_do_the_thing() {
match_err! { (e)
me: MyError => println!("MyError: {}", me),
io: io::Error => println!("IO: {}", io),
unk => println!("unknown: {}", unk)
}
}
I've verified that it's possible to implement this, if we want it :)
In theory, error could:
To accomplish this goal, error would look like this:
struct Error {
inner: usize,
}
This usize would be a pointer to a heap allocation or a vtable. Which kind of pointer it is would be stored using the extra bits of space that alignment guarantees give us on each platform.
The heap allocation would have this layout:
struct Inner {
vtable: &'static (),
backtrace: Backtrace,
data: Fail, // A DST
}
I'm not sure its even possible to write this with unsafe code on stable Rust.
Perhaps I'm doing this wrong, but should Context::with_err
be private?
In attempting to follow the best-practices advice from issue #6, I am attempting to implement my custom error type as a struct
, with enum ErrorKind
variants.
I suppose I'm not following best practices advice by loading a variant with a cause (advice on how best to do this is welcome), but the way I attempted to do this requires access to the Context::with_err static constructor method, which is private. Is this intentional?
Here is my code:
use failure::Context;
use assayer::Error as AssayerError;
#[derive(Debug, Fail)]
#[fail(display = "{}", inner)]
pub struct Error {
inner: Context<ErrorKind>,
}
#[derive(Debug, Fail)]
enum ErrorKind {
#[fail(display = "argument validation error")]
#[cause] ArgValidation(AssayerError),
}
impl From<AssayerError> for Error {
fn from(err: AssayerError) -> Error {
Error { inner: Context::with_err(ErrorKind::ArgValidation(err), err), }
}
}
The above yields error[E0624]: method `with_err` is private
.
If I am using Failure
wrong, my apologies. Suggestions/advice welcome!
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.