Sunday, August 1, 2021

Go Shoot Yourself in the Foot

gopher with a gun

What's the output of the following program?

package main

import (
    "fmt"
)

func foo() (result string) {
    defer func () { fmt.Printf("foo result = %q\n", result) }()
    return "hi"
}

func bar() (result string) {
    defer fmt.Printf("bar result = %q\n", result)
    return "hi"
}

func main() {
    fmt.Printf("foo() = %q\n", foo())
    fmt.Printf("bar() = %q\n", bar())
}

First foo is executed, which will print something, and then the first line of main will print something.

Then bar is executed, which will print something, and then the second line of main will print something.

Here's the output:

foo result = "hi"
foo() = "hi"
bar result = ""
bar() = "hi"

Does the third line surprise you? It sure surprised me. Go ahead, try it yourself.

Why

Reading StackOverflow didn't help. Instead, I had to read the language specification, which is what I ought always to do.

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.

The parameters are evaluated when the defer statement executes, not when the deferred function is invoked.

This is confusing, because one way to defer code is to wrap it in a closure (func) and invoke the closure later on.

Go's defer statement is more general. After thinking about it, I prefer the Go way, except that now I have this hole in my foot.

Be careful out there.