内容简介:About two and a half years ago I wrote a Rust library calledWhen I released failure, the most popular error handling library by far wasFor me, the syntax of error-chain was quite confusing, and I found it verbose and overly complex for the kinds of code I
About two and a half years ago I wrote a Rust library called
failure
, which quickly became one of
the most popular error handling libraries in Rust. This week, its current maintainer decided to
deprecate it, a decision I strongly support. This week, I also released a new and very different
error-handling library, called
fehler
. I wanted to discuss these two libraries briefly.
A brief history of failure
When I released failure, the most popular error handling library by far was
error-chain
.
The only other major error handling library at the time really was a library called
quick-error
. Both of these libraries had the same basic API: using a macro_rules
macro, the user could enumerate all of the different kinds of errors that occur in their program to
create a big error enum.
For me, the syntax of error-chain was quite confusing, and I found it verbose and overly complex for the kinds of code I was writing. My experience was that at the application level, most users are not using errors in a way in which they frequently need to match on them and handle each case differently. Instead, my experience was that most applications followed a common pattern:
-
Fallible code is very common, but errors are not (that is, the code that returns
Result
may be in your hot path, but theError
branch will not be) - Many different kinds of errors can occur during program execution, all of them are handled in the same way
- The way that they are handled is completely agnostic to the kind error, usually some kind of reporting mechanism is involved, perhaps some sort of backpressure (in network applications).
The central idea of failure
then was to use a trait object to model the error type, instead of
enum. I’m not the person who came up with this: it was how the Error
trait in std was always
intended to be used. All I did was investigate why people weren’t already using the Error trait in
its intended way. I came up with these conclusions:
- People often want their errors to contain backtraces, because they may need to debug the condition if it represents a programmer mistake.
-
The
Error::description
method had almost no utility. - People often need their errors to be Send, Sync, and ‘static.
-
People sometimes do need to examine the specific error type, which requires downcasting, which
also requires
'static
. TheError::cause
method, for example, returns an object which cannot be downcast because it isn’t bound by'static
. -
No one feels good writing
-> Result<(), Box<dyn Error + Send + Sync + 'static>>
.
Thus was born the Fail
trait, a “fixed” Error trait. At the same time, failure provided an Error
type which was essentially a Box<dyn Fail>
with some special features. A quick and easy
derive for Fail
was provided to avoid the complex macro syntax of earlier libraries. Adoption was
widespread and rapid.
Failure’s fatal flaw
The big problem with failure was that it required that everyone buy into a new error trait, failure::Fail
. This created compatibility hazards with people who weren’t using failure, resulting
in various frustrating experiences. When I wrote failure, I expected a “fixed” error trait to be
added to std somewhere, that would be identical to failure, so that failure could ultimately just
re-export that trait as failure::Fail
.
However, in the long run, things turned out differently. In the end, I made changes to the Error
trait itself, as described in RFC 2504
. This meant there was no story for making failure
compatible with the std ecosystem.
In the end, many more error libraries have arisen since failure. snafu
for example is a bit
of a spiritual successor to error-chain, but using proc macros. The crate I would recommend to
anyone who likes failure’s API is anyhow
, which basically provides the failure::Error
type (a fancy trait object), but based on the std Error
trait instead of on.
In general, for most libraries I would recommend just manually creating error types and implementing the Error trait for them. If this is too complicated, consider why your API is throwing so many different kinds of errors, and whether your library is doing too many things.
For applications, I recommend using a trait object, rather than an enum, to manage your many kinds
of errors. I believe that usually trying to enumerate all of the errors every library you use can
throw is more trouble than its worth, and I still assert that for most applications this will lead
to higher performance by keeping the happy path less pessimized. I recommend anyhow::Error
as a
strictly better trait object than Box<dyn Error + Send + Sync + 'static>
, not to mention one that
is much easier to deal with.
But about Fehler
When I wrote failure, I also had a vision for syntactic changes to Rust that would make error
handling more lightweight. What I like about the Result
type in Rust is that any function which
can raise an error must be annotated as such, at both the definition side and
the call site. This
is a great feature, that I would never want to lose, because it allows users to identify all points
of early return in their functions (i.e. marking fallible function calls with ?
is an amazing
feature).
What I don’t like about Rust’s error handling story is the impact writing a fallible function has on
all of the unfallible return paths. I do not find that I benefit from writing loads of boilerplate Ok()
expressions. I would prefer a syntax in which I could treat functions as existing only in the
happy path until I need to inspect their fallibility
.
This viewpoint is very controversial, and I have no capacity to debate it with anyone who disagrees with me. But Rust has a very powerful macro system, so I don’t have to.
Fehler is a library which provides two macros: an attribute called #[throws]
and an expression
macro called throw!()
. In functions annotated with throws
, all returns are on the “Ok” happy
path:
#[throws(io::Error)] fn read_to_string(path: &impl AsRef<Path>) -> String { let mut file = File::open(path)?: let mut string = String::new(); file.read_to_string(&mut string)?; string // Ok wrapping not necessary }
If you do want to raise an error in this context, you can, using the throw
macro:
#[throws(io::Error)] fn read_to_string(path: &impl AsRef<Path>) -> String { let mut file = File::open(path)?: let mut string = String::new(); file.read_to_string(&mut string)?; if string.len() == 0 { throw!(io::Error::new(io::ErrorKind::Other, "empty file")); } string }
This syntax also supports functions that return Option and some other features, if you want to know more you can read the docs .
I’ve been using Fehler in a personal project for the last two months, and I love it. My only issue with it has been some bad error messages on parse errors that aren’t processed well because it’s a proc macro. I am as convinced as I ever have been that this would be a great addition to Rust. I would encourage anyone else who would like to try writing Rust with syntax like this to use this library and get a feel for how it works. So much of the discussion around error-handling in Rust has been deeply ideological and ungrounded in specific experience: this library provides a way to get an understanding beyond that.
Besides, I have no interest in pushing any further improvement to Rust’s error handling syntax, because of what an enormous drain every attempt to improve that feature has ever been. So I’m very happy to have fehler, which I will keep using in all of my projects, without having to convince anyone else that it’s a good idea.
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
The Little Schemer - 4th Edition
Daniel P. Friedman、Matthias Felleisen / The MIT Press / 1995-12-21 / USD 40.00
This delightful book leads you through the basic elements of programming in Scheme (a Lisp dialect) via a series of dialogues with well-chosen questions and exercises. Besides teaching Scheme, The Lit......一起来看看 《The Little Schemer - 4th Edition》 这本书的介绍吧!