FWIW, I am pretty happy not using error-chain.
I just read through this thread and then read its documentation on its rustdoc main page. Here are some things I noticed:
-
The documentation spends a significant number of words on the rationale for why error-chain was created instead of deferring to previously existing crates. IMO, the middle of the main documentation page isn’t the place for this.
-
It seems that it defines a Result<E>
type, which I find confusing given the standard Result<R, E>
type. I guess at one time it was considered good to do this. However, I’ve always had bad experiences with this because common coding patterns make the name Result
ambiguous when it’s not qualified, and qualifying it can be a pain. I think we shouldn’t encourage this pattern in any way now, and in particular error-chain shouldn’t encourage it.
-
The source code of a user of this crate consists of a bunch of macro invocations. It isn’t obvious what these macros actually produce. I think this is likely to be compounded if/when procedural macros are used, since presumably the mapping would only get more complex. At the very least, it would be very useful to see, in the documentation, exactly what code the macros generates. Then the reader could easily compare how much boilerplate is saved, and the reader could see what “magic” there is or isn’t. However, I think designs that result in less code generation from macros should be explored, if they haven’t already.
-
I saw this in the example:
fn foo() -> Result<()> {
Err("foo error!".into())
}
Apparently there is some way that strings are convertible to errors. Is this always the case, or is this something that is explicitly enabled? In particular, how does one go about preventing the automatic conversion of strings to the error type? I frequently do manual or (semi-)automated static analysis of code that relies on being able to quickly see which error codes are produced in which circumstances, and this kind of automated creation from strings seems like it would make that very difficult. I think think this kind of analysis is common and IMO the defaults and documentation examples should encourage coding patterns that facilitate it.
At the end of reading the documentation, I asked myself “Do I feel encouraged to use this in my code?” Honestly, I don’t think so, because it doesn’t seem that hard to just do manually what this crate automates, and I think that manually doing it would be clearer (to the reader of the code) than using the macros from this crate. I personally would like an alternative that seems much less magical and preferably one that uses ad-hoc polymorphism (macros) less (ideally not at all) and type-system-based polymorphism (traits) more. Also, to the extent that I want to chain the “cause”, I want to be able to do so without boxing so that the chaining works the same for std and no_std usage.
FWIW, I also disagree with the design of the std::error::Error
trait itself, because I don’t think it makes sense for errors to have human-readable description()
that cannot be localized, and I don’t think it’s important that they implement Display
for the same reason. I see some value in them implementing Debug
though (almost always by #[derive]
). In the epochs discussion it was mentioned that std::error::Error
can never be fixed even with the epochs mechanism. I think it would be great to find some way to define a lighter-weight replacement for std::error::Error
with a better design and then build something like error-chain on top of the new design, so that no_std- and std- targetting crates can work the same.