I've recently been looking into Dart, originally released in 2011, trying to understand why it exists, and whether it's worth using in 2023.
TL;DR
Consider these questions...
- Do you want to write a single UI for mobile and web in Flutter?
- Is iOS execution speed more important to you than using community modules?
- Are you serverless (e.g. Firebase) or happy writing a non-Dart server?
If you answered "YES" to all of these, then Dart/Flutter may be worth a look. If not, I'd say take a look at TypeScript instead. Main reason being, Dart's native-compilation strategy, which is why can run faster on non-JIT platforms like iOS, also makes importing Javascript modules into a Dart project a major interop marshalling mess, because JS can't run on Dart Native, and there isn't (yet) a rich ecosystem of Dart modules for either client or server like there is for Typescript and Javascript.
Dart History
In 2010, the Google-Oracle-Java lawsuit started, and shortly after in 2011, Dart was born. I was no longer at Google at the time, having left late 2009, however, from the external press about Dart, it sounded like Google was hedging an altnative Android development language should the lawsuit create problems for the Java-Android-Dalvik ecosystem. The result was Dart and Dart-Native, and an early version of Flutter. The Supreme Court ultimately decided Google's use of the Java APIs to be fair use, saving our industry from mass chaos, and also making a cumbersome transition of Android developers away from Java to Dart not a priority.
Previous to the lawsuit, there had been increasing work on languages transpiling to Javascript, because of the unweildy nature of large front-end codebases in the
dynamic-typed,
prototype-based,
callback-handler heavy Javascript code style. In 2006, Google had experiemtened with Java-to-Javascript in GWT, with little success. Other transpilation languages targetted code-cleanliness more than static-type checking, such as CoffeeScript (2009) and ClojureScript (2011). While few knew it, in 2006 a little known statically-typed language called Haxe started transpiling to Javascript.
In 2012, Microsoft employed language phenom Anders Hejlsberg released TypeScript, which if you squint a bit looks like Haxe, developed by Microsoft 6 years later, but with lots of robust compiler services and a big company behind it.
In 2013, Google got involved, announcing the Dart-to-JS compiler, which had a different long term vision of pushing browser-runtimes towards the DartVM rather than the other way around. Shortly after, Google proposed AtScript (2014), a TypeScript superset to handle some things Angular needed. When AtScript concepts were merged into TypeScript 1.5, both Angular and React, the two most popular web-frameworks, were standardized on TypeScript, and it effectively "won" typed webdev, again eliminating the niche Dart was hunting for.
And so what is Dart for at this point? Well, there is one nagging little thing that TypeScript/V8 is pretty bad at doing, which is running efficient compiled native code on mobile platforms, such as iOS, which doesn't allow applications to JIT.
What is Dart?
Dart is a statically typed language, more like Java or C# than TypeScript, which is optionally typed. As a result, Dart can be ahead-of-time compiled down to semi-quick native code.
Dart's design uniquely satisfies two tricky competing goals. The first is to be behaviorally like Javascript, so it transpiles to efficient Javascript. It is far from the only language that does this. However, it's next trick is more interesting, the Dart Native ahead-of-time compiler can pre-compile code to run outside a browser. This eliminates the JIT overhead, and on platforms like iOS that forbid JIT and writing to executable code pages, it offers substantially better efficiency than mobile Javascript runtimes (The V8 javascript runtime "no-JIT" mode is an Interpreter on iOS).
Tradeoffs in Efficient Javascript Transpiling
In order to make idiomatic Dart compile to Efficient Javascript, Dart has to remain close to Javascript in behavior. One of the most significant elements of this, is that Dart is a completely reference-type language, like Javascript.
One of the tradeoffs this creates, is that in order to get efficient packed-arrays-of-anything, one has to use the Javascript ArrayBuffer/DataView library APIs. This has overhead and limitations compared to C# parametric instantiated value-types. First, because every access is trampolined through the accessor functions, which hopefully the JIT compiles out, and two, DataViews can not contain object references like C# structs can.
To understand this issue of transpiling to efficient Javascript, one can imagine the opposite compromise, transpiling C# to Javascript. In C#, structs are value types, something as simple as struct Vector2D {float x,y} in C# is *copied* each time it's passed in a function call or assigned to a storage location - this is very fast, and is always faster than using references for structs up to a pretty surprising size. Structs being copied means code can rely on them being immutable (you can only mutate your local stack copy).
If one wants to transpile an idiomatic C# value type struct into Javascript, some tricky compromises insue.
Immutability. In theory, if one controlled all code, we could redefine a new "immutable" property of a reference, and track that around our generated code, assuring that generated code would create a copy before any mutation. However, if any non-generated code ever gets ahold of that reference, it could change it, thus violating our code assumption of value-type-immutability. A safer approach is to copy the Vector2D every time it's handed to a function, to assure immutability. However, this has lots of overhead every time a function is called, when the Vector2D is unlikely to ever be written to.
Equivalence. After we solve this immutability issue, we run into the equivalence issue. Two C# structs are equal if their entire contents are equal, which can be overridden with an Equals() override. However, two Javascript objects are only equal if their references are equal. They can have the same contents, and be not-equal. While one might be tempted create some form of comparison operator on a javascript object, once structs nest objects or objects nest structs, and these things get handed outside of generated code, enforcing the C# behavior expectations get messy fast. One alternative would be to use ArrayBuffer for every C# struct, but this will make the Javascript slower, not idiomatic Javascript, and incapable of storing references.
These are just two examples, but these types of impedence mismatches are why it's difficult to make a language with value type expectations efficiently compile to Javascript.
And so Dart, and Typescript, and Haxe -- as languages which want to efficiently compile to Javascript -- do not have native language value types.
Dart Javascript Interop Mess
The baby that got thrown out with the bathwater in Dart's competing goals of efficient compilation both to Javascript and to native-code, is that interop with Javascript is a total mess.
This is because that statically tyepd Dart code *may* be running in compiled form, and Javascript can't be compiled to static native code, because it's not statically typed. Thus, to include Javascript into a Dart project, Dart assumes that Javascript might actually be running in a completely separate interpreter. Imagine your Dart code compiled to C, which embeds V8 to run your Javascript include. Because that might actually be happening when Dart is compiled to Native.
This blows away the trans
Dart for the Web
When just considering the code-syntax Dart (really Dart 2) is a bit like Typescript, but with stricter static typing, and some differences to enable non-web targets.
One of those big differences is that one can not seamlessly intermix Javascript and Dart, the way one can with Typescript. In Typescript, types are optional, and every valid Javascript file is a valid Typescript file. This is because Typescript always compiles to Javascript.
Even though Dart *can* run inside a browser as transpiled Javascript, this is not the *only* way it can run. As a result, Dart can't seamlessly interop with Javascript. If you include a Javascript module, when your Dart code gets native compiled on iOS, for example, a separate Javascript runtime will handle the Javascript code, which means they don't share the same heap and data, and any data moving between Dart and Javascript will have to be marshaled. Marshaling is expensive. In short, you will be better off *converting* code you'd like to leverage into Dart, than trying to
Dart also has some minor differences, like a slightly different import namespace, to help code accomodate web and native targets togerther. And Dart has a really nifty
method-cascade operator (..), to call many methods on the same object without repeating the object-varaible or having to do small-talk style "return this" methods. Hopefully more languages will adopt this in the future.
Dart Native Ahead of Time Compilation
A huge difference between Dart and Typescript on non-web targets, is that Dart Native supports Ahead of Time Compilation to native code. This is important for mobile, and iOS in particular, because iOS does not allow writing to executable code pages for security reasons, which forbids the use of runtime JIT.
When using Typescript and Reactive Native on Cordova, V8 runs on iOS in no-JIT mode, which uses the V8 interpreter, which can be 40-90% slower than native, depending on the workload. So Dart is going to get you faster iOS code than Typescript for sure.
Dart Native, while offering JIT, has a fairly simplistic two-phase Garbage Collector which is not as good as the current
V8 Orinoco Garbage Collector, and far inferior to Java ZGC or golang's collector. Fortunately, this isn't an issue, as one can also run Dart on the mature V8 JIT runtime anywhere JIT is allowed... and so I fail to see why anyone would actually use Dart Native JIT.
Flutter vs React
The real decision to use Dart or Typescript is driven by the decision to use Flutter or React(-Native), because these ecosystems are tightly tied. It's a Dart/Flutter or TypeScript/React world.
While we're not here to talk about Flutter vs React, Flutter's goal is to enable a single app to run across multiple platforms... which is distinctly different than using React and React Native to use a single language (Typescript or Javascript) to write two distinct apps for mobile and web.
Conclusion
Should Dart exist? Is it worth learning?
Dart was born as a hedge for Google to have independence from Java during the Google-Oracle-Java lawsuit, and has shifted focus over the years to find a useful niche. Today, it offers a Typescript-like language with the unique capability to do ahead-of-time native compilation for iOS and Android while still supporting the fast and mature V8 runtime.
However, this unearths a rather annoying and unique Dart wrinkle known as JS-Interop. Because Dart Native can't *run* javascript, interop with Javascript libraries must assume any javascript code is
However, the use of ramework choice, because one has to use Dart/Flutter instead of TypeScript/React(-Native), there is no practical option today to use React with Dart. Flutter's goal is to write one single app codebase that flexibly runs across platforms (web, mobile, desktop), much like Java Swing back in the 1990s, instead of TS/React's goal to use one programming language to write 2-3 separate apps (mobile, web / desktop).
(see Flutter App Development Cost: Some Things You Should Know | ProCoders)
One big advantage of Dart is being able to use Flutter's more cross-platform focused design. For small teams, or very fast bringup, one can lean less, use fewer tools, and write less code to get running mobile/web/desktop apps with Dart/Flutter than by combining Typescript, React, ReactNative, Cordova, and Electron into a similar ecosystem where one still has to write at least two different distinct apps -- or even more work writing custom Swift/iOS and Java/Android apps.
However, Dart/Flutter comes with some disadvantages as well. From anecdotal experience and word-on-the-street, the Dart compiler and ecosystem are less mature than Typescript and React, and so one is more likely to run into compiler bugs and clunkiness (though I wouldn't rule it out in either ecosystem).
Further, while it's possible to write a simple
server-side in Dart running on Node.JS, and Dart can call into Javascript modules, "nobody is really doing Dart on the server". The Dart-to-JS Interop system is more complex than Typescript, and so the experience is going to be much more manual and clunky than the mainstream TypeScript/Node experience, where one installs javascript modules and typescript type defintionions and is off to the races. Therefore, for more than experimentation, give up on the dream of a single codebase, and expect to write the server in something non-Dart, such as Typescript, Java, Golang, or Python.
Further Reading