Killing

How many times have we all caught ourselves writing code like this:

func makeNext() -> Int {
  let tmp = state
  state += 1
  return tmp
}

We want to manipulate some state, but we also want to return it in its pre-manipulated condition. In other words we’d like to insert a bit of code right after our return statement:

// No, this doesn't work.
func makeNext() -> Int {
  return state
  state += 1
}

But we can’t because nothing after the return will get executed. So instead we stick our state in a tmp variable, do what we gotta do, and then return the tmp.

So what? It’s just one more line of code, right?

The danger here (and with tmp variables everywhere) is that, by definition, they’re exact copies of types we’re actively working with in the same scope. How they differ isn’t semantically clear, and neither the compiler nor code review can stop us from accidentally substituting one for the other:1

func makeNext() -> Int {
 let tmp = state
 state += 1
 return state //uh oh!
}

Thankfully Swift does provide a way for us to squeeze code in after the return of a method — the documentation just doesn’t make this immediately obvious:

Use defer to write a block of code that is executed after all other code in the function…

Yay!

…just before the function returns.

Oh… Um… Well it turns out they’re talking about the function returning on the stack and not the return statement executing in code. So doing something like this works perfectly:

func makeNext() -> Int {
  defer {
    state += 1
  }
  return state
}

This is true regardless of whether state has value semantics or not.

Note this not only avoids confusing our original and tmp variables, but now the intent of our code has been made clear:

Before we couldn’t know why we were forking off a tmp unless we analyzed everything that messed with the original and followed it all the way through to the eventual return of its tmp copy.2 Here, it’s clear from the get-go we want to do some stuff with state. But first we’re going to return it.


1: Actually, in this trivial example, the compiler will warn us that tmp is unused. But we can easily imagine situations where that will not be the case.↩︎

2: And then, if you’re anything like me, all the way back to the top to find where tmp was made in an attempt to remember what it was before we changed it.↩︎