Golang Tidbit: Defer

A while ago, I did a post on Golang Oddities.
I only made one post in what I intended to make a series of, but at any rate,
I’d realized “oddity” wasn’t really the right word. I was intending it more
as an interesting bit to be aware of than knocking the language.

One interesting one to be aware of is how defer works within the language.
A article on how defer, panic, and recover work
briefly mentions something:

A deferred function’s arguments are evaluated when the defer statement is evaluated.

When run, this will print out 0 even though i++ is executed before the call to print.
The way defer works is it does everything it needs to do to get ready to execute an
expression, except it delays the actual execution. So anything that is an argument to
the call is evaluated at the point in the method the defer is at, then executes the
actual expression after the return.

The behavior looks innocuous, but can manifest itself in some auspicious ways. For instance:

This seems normal enough, but now instead of passing in a variable, the arugment
is from a function call on a struct. The same behavior will result. It
prints "Starting" instead of "Done".

However, you also have to be aware of what is being passed into anything being
evaluated. In the above examples, simple non-pointer types were being passed in.
So essentially a copy of the variable was being created and passed to the call
that was being deferred.

In this example, a pointer to a string is being passed to the printStr function.
Because a pointer is being passed in, assignments that happen after the defer
statement are carried over.

So how can this be worked around? The simple way is through an inline function.
Instead of calling what you want to call directly, create an inline function around it.
Evaluating the function at the time is simple, since there are usually no
parameters. But when it is run, it is still in scope of the variables within
the main function.

It is important to note the () at the end. You can’t defer a function type,
you need to defer an expression. So the inline function needs to actually
be called. The same is true with the go keyword to execute a statement in
another goroutine.

Despite some of the gotchas with how defer works, it is definitely one of
my favorite parts of Go. Instead of needing to scatter around cleanup code in a
function, it allows you put cleanup right after dirtying. Say you have to do
5 different things which involve opening files, sockets, etc. Instead of
mucking with cleaning up if the function fails at step 3 and cleaning up #1
and #2, you simply defer the cleanup after each step.

For example, take the following snippet. This is more psuedo code, not any
of our actual code, but in it, we can use a local variable to track if
we succeded, and can check it on the way out to see if everything was
successful.

Another way it could be done is with a named return variable. In the
function definition, give the error object a name and it can be
accessed in the deferred call. If no error is being returned, then
function succeeded.

Overall, defer is excellent to work with and hope you find it awesome too.