Monday, January 19, 2015

Failing to RTFM for Go: defer statements

So, a colleague wanders over the says: "You know that commit you just made to project X? It doesn't work" and the proceeds to remind me of that fact that the defer statement in Go is rather special.

The argument of the defer statement is a function that will be called when the defer statement executes. But its arguments are evaluated when the defer statement is encountered (here's where you, or at least I, RTFM).

A function like this won't do what you expect:

func deferPrintf() error {
var err error
defer fmt.Printf("deferPrintf: err has value %v\n", err)

err = errors.New("Error 2")
return err

That will always output deferPrintf: err has value because the function being called (fmt.Printf) by the defer has its arguments (which include err) evaluated when the defer is encountered.

Oops. I'd forgotten that.

The solution is pretty simple. Wrap the fmt.Printf in a closure like this:

func useAClosure() error {
var err error
defer func() {
fmt.Printf("useAClosure: err has value %v\n", err)

err = errors.New("Error 1")
return err

The err inside the func() is the same err outside (it's a closure after all) and so it does the right thing. You can play with this here.

1 comment:

Ulrich Schreiner said...

you can use this feature to dump runtime statistics :-)

func timed(start time.Time, name string) {
elapsed := time.Since(start)
log.Printff("%s took %s", name, elapsed)

and later:

func myfunction () {
defer timed(time.Now(), "myfunction")

Sorry for the ugly format, i don't know how to post code here :-)