Announcing Dart 2.12
By: Michael Thomsen
Tags: Announcements, Programming, Dart

The Dart platform’s unique capabilities

Before we look at sound null safety and FFI in detail, let’s discuss how they fit into our goals with the Dart platform. Programming languages tend to share a lot of capabilities. For example, many languages support object-oriented programming or running on the web. What really sets languages apart is their unique combination of capabilities.

Dart’s unique capabilities span three dimensions:

Portable : Efficient compilers generate x86 and ARM machine code for devices, and optimized JavaScript for the web. A wide range of targets are supported — mobile devices, desktop PCs, app backends, and more. An extensive set of libraries and packages provide consistent APIs that work across all platforms, further lowering the cost of creating true multi-platform apps.

: Efficient compilers generate x86 and ARM machine code for devices, and optimized JavaScript for the web. A wide range of targets are supported — mobile devices, desktop PCs, app backends, and more. An extensive set of libraries and packages provide consistent APIs that work across all platforms, further lowering the cost of creating true multi-platform apps. Productive : The Dart platform supports hot reload, enabling fast, iterative development for both native devices and the web. And Dart offers rich constructs like isolates and async/await for handling common concurrent and event-driven app patterns.

: The Dart platform supports hot reload, enabling fast, iterative development for both native devices and the web. And Dart offers rich constructs like isolates and async/await for handling common concurrent and event-driven app patterns. Robust: Dart’s sound, null-safe type system catches errors during development. And the overall platform is highly scalable and dependable, with production usage for more than a decade by a large range of apps, including business-critical apps such as Google Ads and Google Assistant.

Sound null safety makes the type system even more robust, and enables better performance. Dart FFI lets you use existing C libraries for better portability, and gives you the option of using highly tuned C code for performance-critical tasks.

Sound null safety

Sound null safety is the largest addition to the Dart language since the introduction of the sound type system in Dart 2.0. Null safety further strengthens the type system, enabling you to catch null errors, which are a common cause of app crashes. By opting into null safety, you can catch null errors during development, preventing crashes in production.

Sound null safety was designed around a few core principles. Let’s revisit how these principles impact you as a developer.

Non-nullable by default: A fundamental change to the type system

The core challenge prior to null safety was that you couldn’t tell the difference between code that anticipated being passed null and code that didn’t work with nulls. A few months ago we discovered a bug in the Flutter master channel where various flutter tool commands would crash on certain machine configurations with a null error: The method '>=' was called on null . The underlying issue was code like this:

final int major = version?.major;

final int minor = version?.minor;

if (globals.platform.isMacOS) {

// plugin path of Android Studio changed after version 4.1.

if (major >= 4 && minor >= 1) {

...

Can you spot the error? Because version can be null, both major and minor can be null also. This bug might seem easy to spot here in isolation, but in practice code like this slips through all the time, even with a rigorous code review process like the one used in the Flutter repo. With null safety, static analysis catches this issue immediately. (Try it live in DartPad.)

Screenshot of analysis output in an IDE

That was a pretty simple error. During our early use of null safety in code internally at Google, we’ve seen much more intricate errors be caught. Some of these were bugs that were known for years, but where the teams hadn’t been able to find the cause without the additional static checks from null safety. Here are a few examples:

An internal team found that they were often checking for null values of expressions that could never be null. This problem was most frequently seen in code using protobuf, where optional fields return a default value when unset, and never null. As a result, code incorrectly checked for the default condition, by confusing default values and null values.

The Google Pay team found bugs in their Flutter code where they would fail when trying to access Flutter State objects outside of the context of a Widget . Before null safety, those objects would return null and mask the error; with null safety, the sound analysis determined that those properties could never be null, and threw an analysis error.

objects outside of the context of a . Before null safety, those objects would return null and mask the error; with null safety, the sound analysis determined that those properties could never be null, and threw an analysis error. The Flutter team found a bug where the Flutter engine could crash if null was passed to the scene parameter in Window.render() . During null safety migration, they added a hint to mark Scene as non-nullable, and were then able to easily prevent potential app crashes that null would have triggered.

Working with non-nullable by default

Once you enable null safety, the basics of variable declarations change because the default types are non-nullable:

// In null-safe Dart, none of these can ever be null.

var i = 42; // Inferred to be an int.

String name = getFileName();

final b = Foo();

If you want to create a variable that can contain either a value or null, you need to make that explicit in the variable declaration by adding a ? suffix to the type:

// aNullableInt can hold either an integer or null.

int? aNullableInt = null;

The implementation of null safety is robust, with rich static flow analysis making it easier to work with nullable types. For example, after checking for null, Dart promotes the type of a local variable from nullable to non-nullable:

int definitelyInt(int? aNullableInt) {

if (aNullableInt == null) {

return 0;

}

// aNullableInt has now promoted to a non-null int.

return aNullableInt;

}

We’ve also added a new keyword, required . When a named parameter is marked as required (which happens a lot in Flutter widget APIs) and the caller forgets to provide the argument, then an analysis error occurs:

Incremental migration to null safety

Because null safety is such a fundamental change to our typing system, it would be extremely disruptive if we insisted on forced adoption. So that you decide when the time is right, null safety is an opt-in feature: you can use Dart 2.12 without being forced to enable null safety. You can even depend on packages that have already enabled null safety, whether or not your app or package has enabled null safety.

To help you migrate existing code to null safety, we’re providing a migration tool and a migration guide. The tool starts by analyzing all of your existing code. Then you can interactively review the nullability properties that the tool has inferred. If you disagree with any of the tool’s conclusions, you can add nullability hints to change the inference. Adding a few migration hints can have a huge impact on migration quality.