Description
Hello,
it seems to me that when people complain about Go's error handling, they mostly complain about the situations when it gets repetitive (otherwise I assume it's not a big problem), like this:
a, b, err := f1()
if err != nil {
return nil, errors.Wrap(err, "failed")
}
c, err := f2(a)
if err != nil {
return nil, errors.Wrap(err, "failed")
}
d, err := f3(b, c)
if err != nil {
return nil, errors.Wrap(err, "failed")
}
Now, I usually don't encounter code like this, but I agree that this looks awful and is hard to prevent sometimes.
So I came up with a simple solution. Keep in mind, the solution is just an idea which might be iterated upon, or completely thrown away.
Assign if nil statement
Here's my solution.
In an assignment statement (=
or :=
), when any LHS operand is surrounded by parenthesis, the assignment will evaluate and assign if and only if all parenthesis-surrounded LHS operands are zero value (nil or an appropriate equivalent)
In case of :=
assignment, variables undeclared before are treated as zero valued.
What I mean is basically that this
a, b, (err) := f1()
gets translated to this
// assume a, b and err are declared
if err == nil {
a, b, err = f1()
}
In general, something like this
a, b, (c), (d) := f()
gets translated to
if c == nil && d == nil {
a, b, c, d = f()
}
How does this help?
With "assign if nil" statement, we can rewrite the original error handling chain like this:
a, b, (err) := f1()
c, (err) := f2(a)
d, (err) := f3(b, c)
if err != nil {
return nil, errors.Wrap(err, "failed")
}
In the first line, err
is undeclared and thus is handled as if it was nil
and f1
executes. Next line only executes f2
and assigns the values if err
is still nil, i.e. when the previous call finished without errors. The same holds for the third line.
In the last line, we nicely wrap the error with additional context information and return it.
Other uses
There are more uses of the "assign if nil" statement. For example, lazily initializing a map:
func (s *Struct) Method() {
(s.m) = make(map[string]string) // this only gets executed if s.m is nil
}
Summary
I propose introducing an "assign if nil" statement. Proper formal specification is not done here, but is easy to imagine.
I think this construct solves most of the hassle with error handling in Go. An if err != nil
once a while is perfectly fine, the only problem is when there is too many of them. I believe that's almost always possible to avoid using this construct.
Looking forward to your feedback!
Michal Štrba