Named return values in Golang. A fickle companion.
Good evening
At first, named return values look like syntactic sugar. They let you define some variables before the function body is executed, and those variables may be omitted from return statement.
The key word is may. You might as well not omit them.
There is a presumption that func foo(a int64) (err error) { is equivalent to
func foo(a int64) error {
var err errorIt might be true in simple cases. Yet
Enter defer
I have found one case where you should be very careful and aware of the results you need.
Simple case
Consider the following example.
package main
import (
"errors"
"fmt"
)
func bar(a int64) error {
if a == 42 {
return errors.New("QEQ")
}
return nil
}
func foo(a int64) error {
var err error
defer func() {
fmt.Println("defer ", err)
}()
err = bar(a)
return nil
}
func main() {
fmt.Println("main ", foo(42))
}The output is
defer QEQ
main <nil>
Pretty logical. err is passed into deferred function by reference, and it prints the err that was there when the function returned. Of course, it doesn’t care what the return value was.
Enter named return values
Now let’s change this example just a bit. I removed the err variable declaration and put it as the named return.
package main
import (
"errors"
"fmt"
)
func bar(a int64) error {
if a == 42 {
return errors.New("QEQ")
}
return nil
}
func foo(a int64) (err error) {
defer func() {
fmt.Println("defer ", err)
}()
err = bar(a)
return nil
}
func main() {
fmt.Println("main ", foo(42))
}If these statements are equivalent, we should get the same output, right? Let’s see the results.
defer <nil>
main <nil>
Whooops. Deferred function got nil, even though we did not set err to nil explicitly. And this is the case that shot me in the leg once (luckily, the functionality wasn’t critical). I expected that defer would get “QEQ” error (and I didn’t want to return the error), but it seems that
err is set to nil under the hood when return nil is called.
If I put a simple return there, the result would be
defer QEQ
main QEQ
But this was not the result I wanted. I needed what we see in the first section. So I had to get rid of named return value there altogether.
In conclusion
Named return values are a powerful tool. But with great power comes great risk. Pretty much every high-level language has a problem of not always being obvious, so be careful with that.
Thanks for reading!