Closed andrewrk closed 2 years ago
Is recent expansion of features related to errors really the proper way to go? Error handling is nuisance, something what should be kept minimum and not encourage people to think about grand design.
I once proposed simple error handling (#122), but what is the current situation?
Errors are grouped, there's inheritance between the groups (hint to create awesome hierarchies) and algebraic operation on them. There also confusing global group.
Functions need to be annotated when they can return error. This annotation is transitive.
The so called catch
is reborn catch(...)
from C++.
The documentation for errors is long (but IMO still under-detailed).
Back to the proposal. Why does one need to compare errors? Error should be correctly handled and that's all about it. The example shown is trivial and impractical.
+1, I found myself wanting this recently. Being able to test bar() == Error.Bad
where bar()
returns Error!T
would by very handy for tests and debug assertions. Yes, std.debug.assertError
covers most cases, but occasionally you need something more complicated (like, say, assert(arg >= 0 or bar(arg) == Error.NegativeArg)
).
For what it's worth, allowing this would also increase consistency between ?T (which already allows == null) and !T. I think this kind of consistency makes programming languages easier to learn.
I know I'm always that guy saying how nice Go is, and I know people complain a lot about Go's error handling, but I honestly do like it and I think it strikes a pretty sane balance between C-like errors (which are typically just special cases of ordinary return values) and more semantic error handling (since Go errors must implement a built-in error
interface, which helps discourage the user from handling many specific kinds of errors in a complicated error API.)
There is a lot of discussion on the topic of Go errors, and I think investigating it is valuable (regardless of your opinion on Go's error handling), because it gets into the user-facing API and semantics of errors.
I think that if Zig errors were not treated specially, then the problem described in this Issue would not exist at all. It would just be another value comparison like any other.
Additionally, I think care should be taken that users are not encouraged to develop a complicated error API. In Go you really have to be quite intentional about exposing an API over your error types. There are a few approaches of worsening usability:
Just hand errors back up the call stack. Don't try to understand them, just check for them. Don't allow comparison, don't expose anything except whether or not there was an error.
Check whether an error value has a particular kind using a package-local boolean checker such as os.IsNotExist(err)
which allows binary decision on an error family but no deeper introspection.
Comparison to specific error values:
switch err {
case os.ErrNotExist:
return Warn{err, fmt.Sprintf("the file %s was missing", path)}
default:
return errors.Wrap(err, "unexpected error")
}
Info
in this example):switch te := err.(type) {
case errs.Critical:
panic(err)
case errs.Warn:
log.Printf("Uh oh: %s", te.Info())
default:
return err
}
This looks both nice and potentially confusing. I am wondering if perhaps it would need some explicit extraction e.g.
assert(foo() =!! error.Bad);
Where =!!
(yeah, I know, bad syntax – but we want something with !
) would do the whole error extraction.
Also, how would one check (in the same manner) that there is an error but the error is not in a particular error set / a particular error?
Interim tip for anyone else who stumbles across this issue:
const is_bad = if (error_union) false else |err| err == error.Bad
Edit: oops, I thought I was in the search UI.