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 error

It 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!