Announcing Dart 2.5: Super-charged development

Michael Thomsen
Sep 10, 2019 · 8 min read

Today we’re announcing the stable release of the Dart 2.5 SDK, which includes technical previews of two major new developer features: ML Complete — code completion powered by machine learning (ML) — and the dart:ffi foreign function interface for calling C code directly from Dart. Dart 2.5 also has improved support for constant expressions.

This release is another step toward our vision of the best client-optimized language for creating fast apps for any platform. ML Complete is a powerful addition to our existing suite of productivity tools like hot reload, customizable static analysis, and Dart DevTools. The second preview feature, dart:ffi, enables you to leverage existing native APIs on the many operating systems where Dart code runs, as well as existing cross-platform native libraries written in C.

Speaking of our ambition to create the best client-optimized language, it was great to see the new IEEE Spectrum Top Programming Language 2019 ratings come out this past week with Dart now included. Dart enters the IEEE Spectrum programming language ratings as #16. It’s also #10 under trending, and #6 when filtering to languages for mobile (behind Java, C, C++, C#, and Swift).

Preview: ML Complete, code completions ranked by machine learning

As APIs grow, exploration becomes difficult, as the list of possible completions gets too long to browse through alphabetically. We’ve been working hard over the past year to apply machine learning to the problem. Simplified, this works by training a model of likely member occurrences based on a given context by analyzing a large corpus of GitHub open-source Dart code. This model — powered by TensorFlow Lite — can then be used to predict the likely next symbol as the developer is editing. We’re calling this new feature ML Complete. Here’s an example of developing a new MyHome widget using the Flutter framework:

Sample developer experience creating a Flutter widget using ML Complete

Let’s take a deeper look at how this works. Imagine that you’re writing a small program to calculate the time that’s one day from the current time. Using ML Complete you get the experience on the left, below. First, notice how it’s able to offer a completion to DateTime.now() based on the variable having the name now. Next, notice how we get a completion on the variable name tomorrow, and finally add(…) is the second offered completion on now. In the non-ML Complete experience on the right, we have to manually start typing DateTime, we get no completions on the tomorrow variable name, and the add(…) method is much further down the list.

Code completion experience with ML Complete (left) and without (right)

How to try out ML Complete

Because the feature is still in preview, the version in the current Flutter and Dart stable releases does not include the performance and polish work that we expect to have in later builds. We therefore recommend that you temporarily use the Flutter dev channel or a Dart dev channel when previewing this feature.

Preview: dart:ffi foreign function interface for Dart-C interop

Currently, support for calling C directly from Dart is limited to deep integration into the Dart VM using native extensions. Alternatively Flutter apps can use C indirectly by calling the host using platform channels and calling onwards to C from there; this is an undesirable double indirection. We aspire to offer a new mechanism that offers great performance, is easy to approach, and works across the many supported Dart platforms and compilers.

Dart-C interop enables two main scenarios:

  • Calling into a C-based system API on the host operating system (OS)
  • Calling into a C-based library, either for a single OS or cross-platform

Calling C-based operating system APIs

The core challenge of any interop mechanism is dealing with the differences in semantics across two languages. For dart:ffi, the Dart code needs to represent two things:

  1. The C function and the types of its arguments and return type
  2. The corresponding Dart function, and its types

We do that by defining two typedefs:

Next we need to load the library and look up the function we’re going to call. How to do this depends on the operating system; in this example we’re using macOS. (We have complete examples for macOS, Windows, and Linux.)

Next, we encode the string argument using the encoding relevant for the particular operating system, invoke the function, and free the argument memory again:

This code executes the system command, causing the system-default browser to open dart.dev:

Executing the system command via dart:ffi to open the default browser

Calling C-based frameworks and components

We also expect that the ability to call C-based libraries will be of great use to Flutter apps. You can imagine calling native libraries such as Realm or SQLite, and we think dart:ffi will be valuable for enabling plugins for Flutter desktop.

Wrapping APIs and code generation

How to try dart:ffi

  • The library doesn’t support nested structs, inline arrays, packed data, or platform-dependent primitive types.
  • Performance of pointer operations is lacking (but can be worked around using Pointer.asExternalTypedData).
  • The library has no support for finalizers (callbacks invoked when an object is about to be garbage collected).

The C interop documentation and dart:ffi API reference document the core concepts and point to examples that you can review. If you experience any issues or have questions, we invite you to post on the Dart FFI discussion group, or file an issue.

Improved constant expressions

Closing thoughts

We’re especially enthusiastic about non-nullable by default. We’ve lined up a pretty ambitious plan for this feature, and have a lot of work underway. A few more recent languages were designed with support for non-nullable from the beginning, while most existing languages that added non-nullable support in a later version settled for a fairly limited approach limited to additional static analysis. One main differentiator of our approach is that we’re aiming for full sound non-nullable support. Briefly explained, this means that our understanding of non-nullability will extend to the core of the type system, and once our type system knows that something is non-nullable, we can fully trust that information, and our backend compiler is free to optimize the code. This soundness has large advantages, both in terms of offering a consistent “no-exceptions experience” and in terms of code size and runtime performance.

We’re always aware of the burden it places on our ecosystem whenever we change the language. Thus, we’re also investing a lot in providing rich migration tooling for existing code. We hope this tooling will offset the majority of the migration cost. We’re also adding a few dedicated language and tool features enabling stepwise migration, and will make an effort to get both our own code and shared code on https://pub.dev migrated.

We look forward to sharing more news later this year!

Dart

Dart is a client-optimized language for fast apps on any platform.