An Overview of Dart, Part 2
The Structure of a Dart Program
Dart, in contrast, has an explicit concept of libraries. More importantly, it omits the idea of code that executes at the top level, rather than in an explicit main() function. This means that a browser with a native Dart implementation is able to parse and even compile Dart libraries as they arrive, irrespective of order (and potentially even in parallel) and so can achieve faster start-up times.
Every Dart program must contain one main() function, which is the program entry point. This gives a strict ordering of program execution, independent of the order in which libraries are loaded.
Concurrency in Dart
Unlike threads, isolates have no shared state with their parent. As an implementation detail, they may share immutable state, but conceptually any data accessed by the new isolate must be copied into it.
If you're familiar with Go, then you will find Dart's concurrency model quite familiar (if you're not, then I would be remiss if I didn't encourage you to read my Go Language Phrasebook). Communication between Dart isolates happens via ports, which are similar to Go channels. Dart is rather more explicit about the directionality of channels, splitting their endpoints into SendPort and ReceivePort instances. Messages can flow down the channel in only one direction, but you can use a pair of ports for bidirectional communication.
You can send primitive values, lists, or maps across a port; and also SendPort instances. When you use the send() method on a SendPort, it implicitly creates a new channel and passes the sending end along with the message (you can also reuse an existing SendPort if you prefer). This means that you can always get a reply to any message that you send, and you can implement synchronous semantics fairly trivially.
There are also some very convenient helpers built on top of SendPorts for you. If you use the call() method on a SendPort, you get a future back. I've talked about futures before a number of times—they're a very powerful and simple concept for concurrent programming. The future is a token that you can keep and then block when you actually need the result.
Futures in Dart are not quite as transparent as in other languages. They aren't proxies, so you can't just use them as if they were, but you can use them for simple synchronization. The then() method on a future takes a function as an argument, which will be passed the result of the future as soon as it completes. You can also use the chain() method to join together a sequence of asynchronous computations.
The last case seems pointless because it effectively means doing the work in a single thread, but it can be very useful for some common concurrent design patterns. For example, you can keep shared data inside a set of isolates and use chained futures to communicate with all of them.