Swift’s for loops avoid an easy mistake with variable capture

This article from a while back points out “the biggest mistake everyone makes with closures” in Ruby, Python, Perl and Javascript.1

Try running this code in Ruby, which counts i up from 1 to 3 creating a closure each time which returns the value of i:

a = []
for i in 1..3
  a.push(lambda { i })

for f in a
  print "#{f.call()} "

and it prints out the possibly surprising value 3 3 3.

Using more “functional” constructs doesn’t always save you. This Python also prints all 3s:2

a = [lambda: i for i in xrange(1, 4)]
for a in f:
  print f()

The article goes on to suggest some workarounds.

Here is the equivalent in Swift:

var a: Array<()->Int> = []

for i in 1...3 {
  a.append( { i })

for f in a {
  println("\(f()) ")

and happily, this prints 1 2 3.

Note, this also works with references to objects, so if you subsituted a for over a sequence of objects instead of Ints in the code above, each closure would capture a reference to a different object.

Be careful though, this applies to for loops but not to other loops like while or the C-style for(;;), so:

var i: Int = 0
while(++i <= 3) {
    a.append( { i })

for f in a {
    print("\(f()) ")

prints out 3 3s.

Why is this? Is this a special hack to make for...in behave less surprisingly? To understand why it does this, you need to realize that this:

for i in 1...3 {
  a.append( { i })

is just a shorthand equivalent to writing this code using the source sequence’s generator:

var g = (1...3).generator()
while let i = g.next()
  a.append( { i })

The answer to our question lies in the let i =, which declares a fresh variable i for each iteration of the loop, hence if you capture i, it will retain its value at the end of the block. If you use this form of while loop, you’ll get the same results as with the for...in version.

  1. Go here for an epic list of how other languages handle this issue. 
  2. The Ruby each will do something similar in version 1.8, though it was altered in 1.9 to behave differently which is somehow more scary. 

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s