A few months ago, I started programming in Dart. I was drawn to it because of Flutter, and the frustrating experience I've had using other Desktop GUI libraries like GTK3, GTK4, FLTK, WxWidgets, AvaloniaUI, Raygui, QT, etc. In my time using the language it was easy to pick up, and I felt like it had a good amount of features for a modern OOP language. But that wasn't always the case, and Dart had to reinvent itself multiple time to get to where it is today. I'll go over some of the history of Dart, and then give some thoughts on my experience using it in this language review. If you'd just like to skip the history lesson and read my thoughts on using the language, jump to the section titled "My Experience with Dart so far".
Origins of Dart
In October 2011, Google unveiled Dart at the GOTO conference in Aarhus, Denmark. The language was developed by Lars Bak and Kasper Lund, both renowned for their work on virtual machines and language design. Dart's initial aim was ambitious... To address the shortcomings of JavaScript and provide a language that could scale from small scripts to large, and complex applications inside of the browser
At the time, web applications were becoming increasingly complex, and JavaScript, despite its ubiquity, was showing its limitations in terms of performance and maintainability for large codebases. Dart was designed to improve on JavaScript by offering optional static typing, a more advanced virtual machine, and improved ergonomics by having more expressive language constructs.
Early Years and Dart 1.x
The initial versions of Dart focused on being a superior language for web development. Developers could write Dart code and compile it to JavaScript using the Dart2js
compiler, allowing it to run in any modern browser, but the ultimate goal was for all browsers to support the Dart VM directly, allowing developers to have another blessed language to program in the browser. Google went as far as getting Dart certified as an ECMA standard with ECMA-408, and created a custom fork of Chromium called "Dartium", which integrated the Dart VM directly. Despite these efforts, Dart struggled to gain traction in the web development community.
In an effort to strengthen Dart's position in web development, Google developed AngularDart, a port to Dart of the popular Angular web framework. AngularDart
offered strong typing and performance benefits over its JavaScript counterpart and was used internally at Google for building complex web applications. However, the broader web development community continued to favor JavaScript, especially after the major updates to the language that came with ES6, and the rise of TypeScript, Microsoft's language that added optional static typing to JavaScript.
In 2015, Google introduced Flutter, a UI toolkit for building natively compiled applications for mobile, web, and desktop from a single codebase. Dart was chosen as the language for Flutter due to its familiar syntax, performant VM, and excellent tooling with tools like Observatory
, a profiler, debugger, and introspection tool. Dart's Hot reload feature also enhanced developer productivity, allowing developers to see code changes in real time without restarting the application. This combination enabled the creation of a GUI toolkit with very fast development cycles, significantly improving developer experience.
Dart 2.0 and a New Direction
But JavaScript was advancing quickly and Dart was failing to gain traction. Recognizing the challenges in replacing JavaScript, Google shifted Dart's focus. In 2018, they released Dart 2.0 with a renewed mission... To optimize client side development for web and mobile platforms. This brought significant changes to the language. They ditched the optional typing and instead went for a sound type system. They removed legacy features and improved the language based on feedback from the Flutter Team, AdWords, AdSense and Dart community. With these changes Dart modified its focus to lean into its ability to compile into efficient JavaScript, its ability to modify programs as they ran through hot reload, and the safety of the language through its sound type system.
On the web front, Dart powered mission critical applications like Google AdWords and AdSense, significantly boosting developer productivity while maintaining performance and reliability. The Team emphasized Dart's importance to Google, noting how it was being used to power a major source of Google's revenue through AdWords. They also highlighted Dart's capabilities in the mobile space by showing off how the official Hamilton: The Musical app, was built using Flutter.
While this shift in focus definitely helped save Dart, it began to sow doubt in the community about whether Google would keep Dart around long term. Google already had Go, had made significant investments in Kotlin, and Dart had failed in its original mission in being the web's next programming language. Given Google's tendency to kill products people began to wonder if Dart was next on the chopping block.
Unfortunately Dart has never been able to shake this feeling even now, 6 years and another major release later. Whenever Google announces layoffs, or kills another product, people start speculating on Dart and Flutters future. The Dart team continues to have to calm the community every time this happens, despite its best efforts. The Dart website even displays a link to the roadmap
prominently at the very top of the page, as if to say "See, Google still has long term plans for this language". This Sword of Damocles over Dart's head hurts its adoption, and is one of the largest reason for people's hesitancy to adopt it as a technology, even though languages like TypeScript have been able to make significant inroads into the web community.
Dart 3.0
In 2023, after five years and numerous incremental releases, Google announced Dart 3.0. The largest update to Dart to date, coming with 3 headlining features. The first was 100% sound null safety which the team had been incrementally working towards for the last 6 years. The "sound part" was a big deal and meant that if a value was declared non nullable, it was guaranteed by the compiler. This along with sound type safety makes Dart a very safe language overall.
The second major feature was Records, Patterns, and Class Modifiers. This gave Dart some additional functional programming features, as well as an increase in its ability to create rich and expressive programs.
Finally, Dart 3 introduced support for compiling Dart code to WebAssembly, enabling the delivery of Dart apps as native code across all modern browsers. Previously, Dart's use of garbage collection hindered its compilation to Wasm, but collaboration with the WebAssembly community led to the development of the new WasmGC feature, now nearing stability in Chromium and Firefox. This allows Dart web apps to have improved load times, and enhanced performance.
My Experience with Dart so far
I've used Dart for 3 projects. A CLI that grabbed the profile sprite of every FREEDOOM and DOOM sprite, so I could compile them into a reference document, a library that parses DOOM wad files so I can understand how the binary format works (notice a trend?), and a File browser app that indexes files by content instead of filename allowing you to do full text search on a specified set of folders. While working on these projects I was exposed to a lot of Dart's interesting features, quirks, and drawbacks, and I learned some new things about programming along the way. Here's what I found
Dart is expressive, and very OOP
This one you'll either love or hate. In Python when writing platform dependent code I usually write it like this
import platform
current_platform = platform.system()
if current_platform == "Windows":
print("Running on Windows!")
elif current_platform == "Linux":
print("Running on Linux!")
elif current_platform == "Darwin": # macOS
print("Running on macOS!")
else:
print(f"Unknown platform: {current_platform}")
In Dart you use a class called Platform
, but instead of doing string comparisons you have specifically defined boolean values like isLinux
or isMacoOs
. You also use things like Platform.environment['HOME']
to get the home directory whereas in Python you'd do os.path.expanduser("~")
. Here is an example in some of my code
String findConfig() {
String configDir;
if (Platform.isLinux) {
configDir =
join(Platform.environment['HOME']!, '.local', 'share', 'TerboMine');
} else if (Platform.isWindows) {
configDir = join(Platform.environment['UserProfile']!, 'AppData', 'Local',
'TerboMine');
} else if (Platform.isMacOS) {
configDir = join(Platform.environment['HOME']!, 'Library',
'Application Support', 'TerboMine');
} else {
throw UnsupportedError('Unsupported platform');
}
return configDir;
}
You work with objects a lot instead of raw strings, which leads to chaining code that looks like this
import 'dart:io';
void main() {
final directory = Directory('/usr/Me/Documents');
final textFiles = directory.listSync()
.whereType<File>()
.where((file) => file.path.endsWith('.txt'))
.toList();
print(textFiles);
}
This makes the language feel like Ruby, Crystal, or even Pharo, which I like because I like those languages, but I understand that is subjective.
One draw back of being OOP means that there are a lot of different modifiers you can add to a classes which change the behavior, and it can get very confusing when a class has many keywords associated with it. This leads me to my next point
There are a lot of keywords
This unfortunately comes with the territory of most statically typed programming languages, but when classes support
abstract
base
final
interface
mixin
sealed
and variables can be const
, late
, or final
, I find myself type masturbating
as I try to craft the perfect type hierarchies. It also clutters the code visually, and I end of making liberal use of var
. In total their are 61 plus or minus a few keywords listed in the Dart reference. As a library author, this is great, and as a consumer of a library the intellisense I get is very informative. But that is double what Python, JavaScript, and Go have, which can be a little overwhelming.
I feel like Dart knows this is an issue, because they try to limit the noise where they can, like how private variables identified by being prefixed with a _
. It also supports the typedef
keyword allowing you to alias types for when they get too long
typedef IntList = List<int>;
IntList il = [1, 2, 3];
Dart Supports Metadata
Dart supports metadata, which are like Python decorators
class Television {
/// Use [turnOn] to turn the power on instead.
@Deprecated('Use turnOn instead')
void activate() {
turnOn();
}
/// Turns the TV's power on.
void turnOn() {...}
// ···
}
One thing I found cool is that you can use this to specify little TODO annotations like in this example
class Todo {
final String who;
final String what;
const Todo(this.who, this.what);
}
@Todo('Dash', 'Implement this function')
void doSomething() {
print('Do something');
}
It's very asynchronous
I'm not used to programming in languages like JavaScript so this was a relatively new concept for me. Dart supports Async Await
, so you'll see methods that are explicitly blocking having Sync
in the name like
final fileDate = file.lastModifiedSync().toIso8601String();
Or you'll write a function and all of a sudden you are returning a List wrapped in a future Future<List<Map<String, dynamic>>>
as in this code
// Search documents in the database using raw SQL
Future<List<Map<String, dynamic>>> searchDocuments(String query) async {
if (query.isEmpty) {
// Return an empty list if the query is empty
return [];
}
final db = await instance.database;
final escapedQuery = _escapeSpecialCharacters(query);
return await db.rawQuery('''
SELECT rowid, name, location FROM $tableDocuments WHERE $tableDocuments MATCH ?
''', [escapedQuery]);
}
but that beats having to litter the code with then
everywhere so I'll take it
instance.database.then((db) {
final escapedQuery = _escapeSpecialCharacters(query);
db.rawQuery('''
SELECT rowid, name, location FROM $tableDocuments WHERE $tableDocuments MATCH ?
''', [escapedQuery]).then((results) {
callback(results);
}).catchError((error) {
// Handle errors here
print(error);
});
});
Since Flutter is Dart's killer app, it makes sense that this type of code pops up everywhere as asynchronous code allows you to create non blocking functions that would otherwise slow down your app if they were synchronous. I believe this is one of the reasons Dart bills itself as a client optimized
programming language, a term I've only ever heard used in the context of Dart.
The tooling is excellent
Dart allows you to try before you buy with Dartpad.dev. There is even sample flutter code so you can see what a simple Flutter app looks like in the browser.
It has extensions for VS Code and Android Studio, as well as an Emacs Major mode, so editor support is good. The Dart cli comes with a lot of different commands allowing you to generate documentation from docstrings, format the code, run tests etc.
A command-line utility for Dart development.
Usage: Dart <command|Dart-file> [arguments]
Global options:
-v, --verbose Show additional command output.
--version Print the Dart SDK version.
--enable-analytics Enable analytics.
--disable-analytics Disable analytics.
--suppress-analytics Disallow analytics for this `Dart *` run without changing the analytics configuration.
-h, --help Print this usage information.
Available commands:
analyze Analyze Dart code in a directory.
compile Compile Dart to various formats.
create Create a new Dart project.
devtools Open DevTools (optionally connecting to an existing application).
doc Generate API documentation for Dart projects.
fix Apply automated fixes to Dart source code.
format Idiomatically format Dart source code.
info Show diagnostic information about the installed tooling.
pub Work with packages.
run Run a Dart program.
test Run tests for a project.
Dart also supports creating projects using the create
command
Dart create --template console-application my_app
Which will generate a folder that typically includes...
pubspec.yaml: Defines your project's dependencies and metadata.
pubspec.lock: Locks the versions of your dependencies.
analysis.yaml: Configuration file to customize the behavior of the static analysis tool.
lib/main.Dart: Main entry point of your application.
test/: Contains test files for your project.
You can generate executables with dart compile
and it has a package manager and a website for browsing packages called pub.dev where I've been able to find pretty much any library I need. This is good because of my next point...
The Standard Library is kind of small
Because Dart has a large mobile and web focus, It has to be careful about features that could mess with the highly sandboxed nature of Android and IOS. This means the standard library that comes with a vanilla install only covers these core areas
Dart:core
Built-in types, collections, and other core functionality. This library is automatically imported into every Dart program.
Dart:async
Support for asynchronous programming, with classes such as Future and Stream.
Dart:math
Mathematical constants and functions, plus a random number generator.
Dart:convert
Encoders and decoders for converting between different data representations, including JSON and UTF-8.
Dart:io
I/O for programs that can use the Dart VM, including Flutter apps, servers, and command-line scripts.
Dart:html
DOM and other APIs for browser-based apps. We now recommend using package:web over Dart:html.
To get around this, you'll notice when browsing pub.dev
that the Dart Team publishes under three different publishers
Dart.dev - Core packages. Foundational packages that complement the core libraries.
tools.Dart.dev - Tooling packages. Used by the Dart team to build various Dart tools. Can be used for building other tools, but the support commitment is lower than for core packages.
labs.Dart.dev - Experimental packages. Shared for early feedback. Some will likely be discontinued.
Many of the additional packages I needed where available from the Dart.dev
, and the tools.Dart.dev
publishers. But it seemed a little silly that I had to download the yaml package which is maintained by tools.Dart.dev
, considering that every Dart project includes a yaml file. On the other hand, a smaller standard library meanings easier porting to different platforms, and smaller programs overall, so it is a trade off.
One challenge with Dart running on so many platforms is that you have to be careful with using certain features in the standard library or the ecosystem at large. For example... In the dart io library you'll see the message
/// **Important:** Browser-based apps can't use this library.
/// Only the following can import and use the dart:io library:
/// - Servers
/// - Command-line scripts
/// - Flutter mobile apps
/// - Flutter desktop apps
There are also certain libraries on pub.dev
which don't work with Flutter, Dart's killer app, and these tend to be less popular.
Dart code is very Tall
Dart compared to other languages I've used, loves its vertical space. It loves its trailing commas (I've become pro trailing comma), and will line break on every one, which leads to code like this in one of my apps
"validExtensions": [
".txt",
".md",
".rst",
".yaml",
".yml",
".json",
".xml",
".csv",
".tsv",
".c",
".cpp",
".h",
".hpp",
".java",
".class",
".py",
...]
This is definitely influenced by Flutter, which has a declarative nested syntax, but I don't consider that a bad thing, because the language is 'client optimzied' and the minute you start highlighting widgets and using the Wrap with padding
, Extract Widget
, Extract Method
etc, you start seeing and editing code as structured chunks which is hard to explain if you've never experienced it.
Concurrency in Dart
Dart provides robust support for concurrent programming using Async Await
patterns, and Isolates. Isolates allow you to create independent units of execution that can run concurrently on multi core processors. This enables efficient handling of computationally intensive tasks without blocking the UI thread.
Isolate Isolate = await Isolate.spawn(heavyComputation, data);
Isolate.receivePort.listen((message) {
// Process the result from the Isolate
});
Isolates offer several advantages over traditional thread-based parallelism including...
Memory Safety: Isolates have their own memory space, preventing them from interfering with each other. This eliminates the risk of data races and other concurrency related bugs that are common in thread based systems.
Simplified Concurrency: Dart's Isolate model simplifies concurrent programming by providing a more intuitive and less error prone approach. You can spawn Isolates to handle independent tasks while enjoying a higher level of abstraction.
Efficient Resource Utilization: Isolates can be efficiently scheduled on multiple cores, allowing you to take full advantage of modern hardware.
Graceful Error Handling: If an Isolate crashes, it won't affect the rest of your application. This makes your application more resilient to errors and failures.
Isolates are no silver bullet though as they do come with some downsides. There is overhead that comes with spawning Isolates, so you have to weigh the startup cost of spawning an Isolate vs just letting the task complete synchronously. There is also overhead when passing messages in Isolates when compared to direct memory access in traditional threads, whichs makes them less efficient. Finally, Isolates can cause weird behavior in some libraries on certain platforms, like when trying to use the popular graphics framework Raylib on macos
Still, Isolates are a good solution when you still want to have parallelism but want to avoid some of the pitfalls associated with it. Given Dart's focus on frontend development, incurring the performance hit for safer parallelism seems like a good tradeoff. And the fact that Isolates make it easier than traditional threading make people more inclined to actually parallelism where they can.
Dart Does Tree Shaking but there is an asterisk
Tree shaking, AKA Dead code elimination, results in smaller code sizes due to only including code in your app that you actually use. This is an awesome feature, and not one that is common in programming languages. Unfortunately this is also under documented in Dart, and it is unclear what is and isn't tree shakeable. This has been raised in this Github Issue, but there has been no resolution to it yet.
It has a large Medium community
This is a interesting one. There are a lot of Dart articles on Medium. I think this is because both Dart and Flutter announcements are syndicated to Medium like the annoucent of Dart version 3.5 and have been for a long time.
There are a lot of Materials about Flutter
Dart can be used for both backend and frontend development, but Flutter is king. This does come with a huge benefit though, pretty much everyone in the Dart community knows Flutter, and the amount high quality tutorials teaching it are numerous compared to the size of its community
This is very different from the Python world where I live that has many people using different subsets of the language. For instance Data Scientists use Pandas, NumPy and Matplotlib, ML People use TensorFlow, Scikit-learn, and Keras, and web developers using use Django or Flask. The drawback to this is that if you aren't doing anything Flutter based you are kind of on your own, but I believe with libraries like Jaspr, Dart Frog, ml_algo, and many others on awesome-Dart that is changing.
It's pretty fast
1 billion loop challenge
Benchmark Games
Micro benchmarks aren't entirely representative of a programming language’s performance. But people love them as seen by Twitter’s viral billion loop challenge, and the success of the billion row challenge last year.
With that being said Dart is fast for a garbage collected language. When your virtual machine was created by the same guy who made the V8 JavaScript engine (Lars bak), and the language was designed to be easier to optimize than JavaScript, you end up with a language that is pretty quick.
It has a cool Mascot (Objective)
As is tradition, Dart has an animal mascot and her name is Dash. She is a Hummingbird and there is a website dedicated to creating your own version of a Dashatar.
TLDR/Conclusion
Dart is an interesting language that is in a spot most programming languages would kill to be in. It has corporate investment and a team of people who are paid to develop it. It has a killer app through Flutter that has large total addressable market (anywhere a GUI can run).
It is cross platform across all 3 major desktops, including mobile which many programming languages struggle to target. It is capable of compiling an exe with dart compile exe bin/main.dart
, and compiling to the browser with Wasm and JavaScript. And its foundation is built on cutting edge programming language theory. Yet it has been overlooked compared to other languages that have come out in a similar time frame namely TypeScript (2012), Kotlin (2011), Go (2009), Rust (2010), and Swift (2014).
While the language is not perfect, I hope you might consider given the language a shot in the future (maybe in this years advent of code?), and I will continue to use it in my personal projects going forward. If you are interested in more Dart content, I have a Flutter article coming soon, where I will speak to my experience learning and using it. Stay tuned!
Call To Action 📣
Hi 👋 my name is Diego Crespo and I like to talk about technology, niche programming languages, and AI. I have a Twitter, Mastodon, and Threads if you’d like to follow me on other social media platforms. If you liked the article, consider liking and subscribing. And if you haven’t why not check out another article of mine listed below! Thank you for reading and giving me a little of your valuable time. A.M.D.G