Quick tips: How to change function return value from deferred call

It is common to use defer in Golang and there are some cases where you could want to check if the deferred call returned an error or not. In such a case, how could you check and pass the deferred error to the function caller?

The example below shows an example of using defer with Close():

func createFile(name string) error {
	f, err := os.Create(name)
	if err != nil {
		return err
	}
	defer f.Close()

	return nil
}

If your function cannot use the direct return value from f.Close() (for example, if your function has more logic to be done), just using defer you would ignore the possible error.

There is a way to handle this case with defer by using Named Return Values. Note the the difference in the return of the function below compared to the example given earlier and the defer implementation:

func createFile(name string) (err error) {
	f, err := os.Create(name)
	if err != nil {
		return err
	}

	defer func() {
		cErr := f.Close()
		if err == nil {
			err = cErr
		}
	}()

	return nil
}

The err variable is declared in the return, so it’s a local variable declared in the function signature. You can think of it with the same behavior of the name parameter variable. This feature is the reason that allow us to overwrite the err value inside the defer func() block.

To make it even clear, lets force an error from the f.Close() call inside the defer func() block – to do that, we are just inserting one more f.Close() right after the if err check:

func main() {
	fmt.Println(createFile("example.txt")) // output: close example.txt: file already closed
}

func createFile(name string) (err error) {
	f, err := os.Create(name)
	if err != nil {
		return err
	}
	f.Close()

	defer func() {
		cErr := f.Close()
		if err == nil {
			err = cErr
		}
	}()

	return nil
}

When you run the above code, the output will be: close example.txt: file already closed. If we rewrite the method to the not checking deferred error mode, we can see that this error is omitted and could cause some hard problems to debug in the application:

func main() {
	fmt.Println(createFile("example.txt")) // output: <nil>
}

func createFile(name string) error {
	f, err := os.Create(name)
	if err != nil {
		return err
	}
	defer f.Close()

	return f.Close()
}

Conclusion

Using defer doesn’t mean that you can’t check for possible errors from the deferred function call – with Named Return Values, it is possible to check for them and overwrite the function returned value to pass the error to the caller. If you want to understand more about the topic, check out the: Defer, Panic, and Recover Go blog post!

If you have any questions, suggestions or want to discuss the topic even further, reach me out on Twitter!