Modifying return values in deferred functions

August 27, 2024

Oops!

It seems that Friday’s email got stuck, and didn’t actually send until late Monday. Meaning you probably got two emails in the same day, and out of order. I hope it didn’t bother anyone too much.

I should probably make a joke about how it was deferred… Nah!


Today we’ll be finishing up the section on the defer keyword, with one special capability that it affords us.

Defer statements

For instance, if the deferred function is a function literal and the surrounding function has named result parameters that are in scope within the literal, the deferred function may access and modify the result parameters before they are returned. If the deferred function has any return values, they are discarded when the function completes. (See also the section on handling panics.)

lock(l)
defer unlock(l)  // unlocking happens before surrounding function returns

// prints 3 2 1 0 before surrounding function returns
for i := 0; i <= 3; i++ {
	defer fmt.Print(i)
}

// f returns 42
func f() (result int) {
	defer func() {
		// result is accessed after it was set to 6 by the return statement
		result *= 7
	}()
	return 6
}

As we can see in the example presented, a deferred function has the ability to set a named return value, before it is actually returned.

This only works for named return values. And is what allows recovery from a panic (which we’ll get to soon).

For now, the main thing I’d like to draw your attention to is the relationship, and distinction with, naked returns.

Frequently, when warning against the use of naked returns, someone will point out the benefit of being able to modify a named return value in a deferred statement as an argument in favor of naked returns.

While both of these features, modifying return values in a deferred function, and naked returns, depend on named return values. However, they are distinct features, and the use of one does not imply the use of the other.

Quotes from The Go Programming Language Specification Language version go1.23 (June 13, 2024)


Share this

Direct to your inbox, daily. I respect your privacy .

Unsure? Browse the archive .

Related Content


Return statements, conclusion

Just a couple small notes before we move on from return statements… Return statements … Regardless of how they are declared, all the result values are initialized to the zero values for their type upon entry to the function. A “return” statement that specifies results sets the result parameters before any deferred functions are executed. For the most part, this can be considered trivia, for Quiz night at your next Go meetup.


Naked returns

Now we’re going to look at the third and finaly way to return values from a function in Go. But first a warning. ⚠️ NEVER DO THIS!! ⚠️ What we’re about to look at is commonly referred to as “naked returns” or “bare returns”, and it is widely (though, admittedly, not universally) considered bad practice. It’s honestly a mystery to me why this feature exists in Go, and I wish it didn’t.


Defer execution order

Let’s talk about some of the nuance surrounding defer that’s often lost, or simply forgotten. Defer statements … Each time a “defer” statement executes, the function value and parameters to the call are evaluated as usual and saved anew but the actual function is not invoked. Instead, deferred functions are invoked immediately before the surrounding function returns, in the reverse order they were deferred. That is, if the surrounding function returns through an explicit return statement, deferred functions are executed after any result parameters are set by that return statement but before the function returns to its caller.

Get daily content like this in your inbox!

Subscribe