Exploring Defers
Sources
When first learning go the immediate keyword that stands out to most developer is probably defer. It is probably one of the most unique thing about go and requires a small shift in thinking.
defer foo()
In the Defer, Panic, and Recover article the example provided is that one of opening and closing a file. Lets take a closer look at some of the interactions can playout using defers.
type SafeVal struct {
mu sync.Mutex
val int
}
func (sv *SafeVal) Set(i int) {
sv.mu.Lock()
defer sv.mu.Unlock()
sv.val = i
}
func (sv *SafeVal) Get() int {
sv.mu.Lock()
defer sv.mu.Unlock()
return sv.val
}
At work, usually am trying to aggregate data from multiple services and creating a clean copy in a centralized structure. So, I personally find this pattern super helpful when working with a critical section that needs to be extracted. And most of my interaction with defer if a simple function call. But let’s take that a little further and explore the interaction between defer, closures and pointers. The blog states that the “A deferred function’s arguments are evaluated when the defer statement is evaluated.”
func BasicDefer(i int) int {
defer fmt.Println("Inside Defer", i)
i++
return i
}
func main() {
k := 3
fmt.Println("Value Returned", BasicDefer(k))
}
Inside Defer 3
Value Returned 4
Sounds simple enough. But what if you need to print it twice. Well there are two ways immediate ways to do that. One is that you can just add aother defer and print again. And remeber defer follows LIFO (last in first out) so we will have something like this.
func BasicDeferTwo(i int) int {
defer fmt.Println("Inside Defer B", i)
defer fmt.Println("Inside Defer A", i)
i++
return i
}
Inside Defer A 3
Inside Defer B 3
Value Returned 4
Ok, that does what we need it to do. Now lets try the second approach where we combine them into a singular defer.
func ComplexDefer(i int) int {
defer func() {
fmt.Println("Inside Defer A", i)
fmt.Println("Inside Defer B", i)
}()
i++
return i
}
Inside Defer A 4
Inside Defer B 4
Value Returned 4
Well, that’s slightly unexpected. We accidently created a closure. This is super easy to do and many of us may not even realize that we did that. This can result in an unexpected behaviour. Remeber that the first rule of defer was that the arguments of deferred function is evaluated at statement evaluation. so we can easily fix that bug by pass in the parameter that needs to be evaluated as such. This is a pretty easy trap to fall into, so a warning is required.
func ComplexDefer(i int) int {
defer func(i int) {
fmt.Println("Inside Defer A", i)
fmt.Println("Inside Defer B", i)
}(i)
i++
return i
}
Inside Defer A 3
Inside Defer B 3
Value Returned 4
Now that we have seen some of the interactions between defer and closure lets take a look at how pointers interact with defers. Let’s take the basic example we looked at in the begining of the article and try working a pointer instead of a literal.
func BasicDefer(i *int) int {
defer fmt.Println("Inside Defer", *i)
*i++
return i
}
Inside Defer 3
Value Returned 4
Even when switching to the pointer the behaviour here is the same as our previous BasicDefer example. And this is because *i is being derefernced and the value is being passed in as a copy into the Println. so if we were to change the example to use the ComplexDefer as such.
func ComplexPointerDefer(i *int) int {
defer func() {
fmt.Println("Inside Defer", *i)
}()
*i++
return *i
}
Inside Defer 4
Value Returned 4
Now we can easily see that this behaviour is expected. Since the dereference happens inside the function. And in this case it will not matter if the value pointer value is passed in since the initial value is a pointer and not a copy of the original value.
func ComplexPointerDefer(i *int) int {
defer func(i *int) {
fmt.Println("Inside Defer", *i)
}(i)
*i++
return *i
}
Inside Defer 4
Value Returned 4
However as many of you may have guessed it. We can simply deference the pointer at the moment of invocation and we will yeild the original result.
func ComplexPointerDefer(i *int) int {
defer func(i int) {
fmt.Println("Inside Defer", i)
}(*i)
*i++
return *i
}
Inside Defer 3
Value Returned 4
Hopefully this clarifies some of the weirdness you may have encounted when working with defers and anyonomous functions.