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.
They offer up a simple code snippet to highlight the fact:
When run, this will print out
0 even though
i++ is executed before the call to print.
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
"Starting" instead of
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.
On the other hand, take the following example:
In this example, a pointer to a string is being passed to the
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
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
defer is excellent to work with and hope you find it awesome too.