Throwing Darts

Front end development for the rest of us.

31

OCT
2014

How to Avoid Taking a Dart to the Knee

I've been playing with Dart quite a bit lately. I really enjoy the language, but there are always snags that trip up those coming from other backgrounds. Here are the top three issues that have bit me in Dart, in the hopes of saving others some pain:

The Truth and Nothing But the Truth… Literally!

One of the challenges of any language is figuring out what it considers to be truthy in conditional expressions. Each system has its twists, but I find Dart to be extra strict in this case.

Here's some code illustrating the rule:

bool isTruthy(Object condition) {
  return !!condition;
}

void main() {
  var tests = [true, false, null, 42, 0, "", [ ], new Object()];
  for (var test in tests) {
    print("$test is ${isTruthy(test)}");
  }
}

That outputs:

$ dart truthiness.dart
true is true
false is false
null is false
42 is false
0 is false
 is false
[] is false
Instance of 'Object' is false

As you can see the literal true (just that one object) is truthy in Dart and everything else is considered false. I'm in the habit of playing pretty fast and loose with truthiness from all of my time working with Ruby, so this has surprised me a few times.

The best defense, in my opinion, is to be really strict with the return values in places where you are expecting booleans (bool in Dart). I recommend using only true and false.

Dart itself has the same recommendation, by the way. If you run the code above in checked mode, Dart errors out on the first non-bool value:

$ dart -c truthiness.dart
true is true
false is false
Unhandled exception:
type 'Null' is not a subtype of type 'bool' of 'boolean expression'.
#0      isTruthy (file:///Users/jeg2/Desktop/truthiness.dart:2:12)
#1      main (file:///Users/jeg2/Desktop/truthiness.dart:8:31)
#2      _startIsolate (dart:isolate-patch/isolate_patch.dart:239)
#3      _startMainIsolate.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:192)
#4      _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:130)

There are two things you should notice here:

  • Checked mode, like Ruby's warnings, gives you free debugging help!
  • Even null failed to qualify as a bool in conditionals. (Don't get too excited yet, until you read the next gotcha…)

Escaping From Alcatraz and null are Equally Hard

If you love how null triggered an error in my last example and/or you've spent any time playing Rust's crazy elegant type system, you're almost surely about to be disappointed.

Here's a seemingly harmless chunk of code:

bool liesDamnLiesAndNull() {
  return true;
}

void main() {
  print("liesDamnLiesAndNull() returned ${liesDamnLiesAndNull()}");
}

That prints:

$ dart null_returns.dart
liesDamnLiesAndNull() returned true

I assume we're fine with this exercise so far. But let's tweak that function a few ways and watch the surprises roll in. Here's the first experiment:

bool liesDamnLiesAndNull() {
  return [ ];
}

Dart kind of allows this:

$ dart null_returns.dart
liesDamnLiesAndNull() returned []

Don't fret though! "Mind what you have learned. Save you it can." Checked mode to the rescue:

$ dart -c null_returns.dart
Unhandled exception:
type 'List' is not a subtype of type 'bool' of 'function result'.
#0      liesDamnLiesAndNull (file:///Users/jeg2/Desktop/null_returns.dart:2:10)
#1      main (file:///Users/jeg2/Desktop/null_returns.dart:7:62)
#2      _startIsolate (dart:isolate-patch/isolate_patch.dart:239)
#3      _startMainIsolate.<anonymous closure> (dart:isolate-patch/isolate_patch.dart:192)
#4      _RawReceivePortImpl._handleMessage (dart:isolate-patch/isolate_patch.dart:130)

So we're still OK, right? Well, here comes the bad news:

bool liesDamnLiesAndNull() {
  return null;
}

That gets a pass, even in checked mode:

$ dart -c null_returns.dart
liesDamnLiesAndNull() returned null

I guess the logic is that a bool variable can hold a null (makes sense to me as it might not have been initialized), so a bool return value has to mean true, false, or null (makes no sense to me). This isn't just about the bool type either. All return values include null.

Now, I hate to kick a reader while they're down, but… well… brace for impact:

bool liesDamnLiesAndNull() {
  true;
}

Again, we're still in checked mode:

$ dart -c null_returns.dart
liesDamnLiesAndNull() returned null

Ouch. Am I right?

Obviously, the last expression is not automatically returned for you in this language. Worse, if a statement of the form return whatever; is not executed by your code, Dart will add a return null; as the last thing the function does.

This is the biggest item I have disagreed with in the design of Dart so far. I would much prefer it just throw errors as it did for the conditional earlier. It could do so in checked mode, at the very least. null bugs are just too insidious as even their creator knows.

OK, we did the bad surprise. It's time for the good surprise.

Can You Die of Laziness?

In a word: yes. Here's the code:

void main() {
  var current_list = new List.generate(100, (i) => i);
  while (true) {
    current_list = current_list.where((_) => true);
  }
}

Here's the proof of death:

$ dart -c lazy_iteration.dart
Exhausted heap space, trying to allocate 32 bytes.
Exhausted heap space, trying to allocate 32 bytes.
Exhausted heap space, trying to allocate 32 bytes.
…

I ran into this issue in a Web page where I had a simple list of items that I was tracking. Each time through the event loop, I would filter out the items I no longer needed to track, using code like you see above. In a browser, this eventually generated stack overflow errors.

The good news is that it's:

  • All my fault
  • Easy to fix
  • And for a good cause, in my opinion

Again I'm use to Ruby where iterators are immediate, unless you explicitly make them lazy. In Dart it's often the opposite. Many iterators, like where() above, are just lazy by default. (Rust works this way too.)

I'm a big fan of the lazy-by-default behavior, so don't take this point as a complaint. You just need to remember to force the iterator to be evaluated when needed, which is easy to do:

void main() {
  var current_list = new List.generate(100, (i) => i);
  while (true) {
    current_list = current_list.where((_) => true).toList();
  }
}

That code can run forever.

It's Not All Bad

I'm kind of sad that my first post about Dart ended up being kind of a downer. I'm having fun playing with the language. It's good stuff, if you ask me. You'll just have to wait for the followup articles where I show off the good parts…

Comments (0)
Leave a Comment (using GitHub Flavored Markdown)

Comments on this blog are moderated. Spam is removed, formatting is fixed, and there's a zero tolerance policy on intolerance.

Ajax loader