ziglang / zig

General-purpose programming language and toolchain for maintaining robust, optimal, and reusable software.
https://ziglang.org
MIT License
34.34k stars 2.51k forks source link

ability to test equality of error unions and error codes #1302

Closed andrewrk closed 2 years ago

andrewrk commented 6 years ago

Edit: oops, I thought I was in the search UI.

const assert = @import("std").debug.assert;
test "aoeu" {
    assert(foo() == error.Bad);
    assert(bar() == error.Bad);
}
fn foo() error {
    return error.Bad;
}
fn bar() error!void {
    return error.Bad;
}
PavelVozenilek commented 6 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?


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.

mdsteele commented 6 years ago

+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.

binary132 commented 6 years ago

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:

  1. 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.

  2. 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.

  3. 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")
}
  1. Implement specific types to create error families and use reflection to check family and expose internal APIs (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
}
  1. This shouldn't even be considered a solution, but you can always just panic and unwind.
lerno commented 5 years ago

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?

jayschwa commented 4 years ago

Interim tip for anyone else who stumbles across this issue:

const is_bad = if (error_union) false else |err| err == error.Bad