premise: Errors are values. Unless some fundamental constraint has been violated, in which case feel free to panic, errors are just some other state to be handled.
pain points: as far as I can tell, there are 2 main complaints:
For the first issue, people usually propose some syntax sugar
to shorten if err != nil { ... }
How about a slightly more generic solution that checks against the zero value,
then we can coopt the short circuiting behaviour of &&
to make the common case one line.
A new unary operator !!
evaluates to true if the operand is the zero value of its type, else false.
1_, err = f() // currently valid
2err != nil && return _, _, fmt.Errorf("foo: %w", err)
3
4_, err = g() // proposed, pre
5!!err && return _, _, fmt.Errorf("bar: %w", err)
6
7_, err = h() // proposed, post
8err!! && return _, _, fmt.Errorf("fizz: %w", err)
9
10// equivalent function
11func z(v interface{}) bool {
12 return reflect.ValueOf(v).IsZero() // but that reflect penalty...
13}
14
15_, err = i()
16z(err) && return _, _, fmt.Errorf("buzz: %w", err)
Also really want _
to mean the zero value for any type...
I also thought the idea of assigning to an error handler was a good start, though it does have some issues, like implicit passing of arguments, handler having to be defined in function scope, and non local returns, I guess with some extra thought this is where the check/handle came from.
1
2func f() (err error) {
3 handler := func(other arg, err error) {
4 if err != nil {
5 err = fmt.Errorf("some extra context: %w", err)
6 return // non local return???
7 }
8 }
9
10 var o arg
11
12 _, handler(o) = g()
13}
14
15func handler(err error, arg string) {
16 if err != nil {
17 return
18 }
19}
20
Usually the people focusing in the second camp want sum types, forcing you to handle errors. People usually deal with this now with linters, and to be honest, I don't really think this is as much of a concern