For loops in Dart, or, Fresh bindings for sane closures

(This is Part 6 of an ongoing series about Dart. Check out Part 5, Generics in Dart.)

Dart has two forms of the for loop. While you may familiar with this form of iteration, the JavaScript developer will find one (surprisingly refreshing) difference. Read on!

Dart supports the typical for loop form:

for (var i = 0; i < 3; i++) {
  print(i);
}

As well as the for-in form:

var collection = [0,1,2];
for (var x in collection) {
  print(x);
}

The for-in form is sugar for the longer code which involves an iterator:

// this is the long form of for-in

var n0 = collection.iterator();

while (n0.hasNext()) {
   final x = n0.next();
   print(x);
}

// where n0 is an identifier that does not occur anywhere in the program.

This means that any object which implements Iterable can be used via for-in.

Hopefully this looks familiar so far. You might even be yawning. But wake up!

There is a subtle semantic difference between how Dart runs the for loop and how JavaScript runs the for loop.

To start, let's look at the following JavaScript code:


When this runs, what do you expect to be printed to the console? If you said 01 you'd be incorrect. This will, in fact, print 22. Go ahead, give it a shot.

What is happening here? JavaScript is binding the same variable to each of the anonymous functions inside the first for loop. Every callback sees the same i. This is certainly non-obvious to the beginning JavaScript developer.

The solution is to remember that JavaScript has function level scoping (not block level scoping). Wrapping the anonymous functions inside their own anonymous, yet immediately executed, function will create a "private" copy of the variable. For example:


We can do better! We should demand better!

Dart makes this a whole lot easier and arguably more approachable. Consider the following Dart code:

main() {
  var callbacks = [];
  for (var i = 0; i < 2; i++) {
    callbacks.add(() => print(i));
  }
  callbacks.forEach((c) => c());
}
> 0, 1

The above code is syntacticly similar to our first JavaScript example, yet behaves like the second JavaScript example. Yes, that means this prints 0 and then 1! Hurray!

Note: the for-in style loop will also get a fresh binding for i, just like the standard for loop.

If you squint, the Dart language spec specifies this exact behavior, with the following quote:
[it is] intended to prevent the common error where users create a closure inside a for loop, intending to close over the current binding of the loop variable, and find (usually after a painful process of debugging and learning) that all the created closures have captured the same value - the one current in the last iteration executed.
Warning: As of the time of this writing (2012-01-10), only the Dart VM does the right thing for the for loop. Unfortunately, DartC and Frog (the two Dart -> JavaScript compilers) are broken. Track bug 333 to follow this bug.

Further good news: ECMAScript 6 should allow for simplified syntax thanks to block scoping:

let callbacks = [];
for (let i = 0; i < 10; i++) {
  let j = i;
  callbacks.push(function() { print(j) });
}
callbacks.forEach(function(c) { c() });

Summary

Dart has two familiar forms of the for loop for iteration. In both forms, a fresh copy of the variable created by the loop is generated. Closures and anonymous functions inside of for loops will see individual variables, which is generally a more expected behavior.

Special thanks to +Bob Nystrom and his informative Dart posts on Reddit, such as this gem which provided the inspiration for this post.

Next Steps

Learn more about Dart, play with Dart in your browser, browse the API docs, enter feature requests and bugs, and join the discussion. And remember, Dart is in Technology Preview right now, things are changing and we'd like to hear your feedback!

Also, read Part 7: Strings in Dart.
7 comments

Popular posts from this blog

Converting Array to List in Scala

I ported a JavaScript app to Dart. Here's what I learned.

Minification is not enough, you need tree shaking