Flutter app development tutorials by Andrea Bizzotto

Yay! Implicit Downcasts are no longer allowed in Dart 2.9

With the introduction of full Type Safety, Dart 2.9 makes it easier to write safer code by default.

As part of this, implicit downcasts are no longer allowed.

What is an implicit downcast?

Consider this code:

String s = 'hello world'; Object o = s; // upcast, allowed

You can always assign a String value to an Object variable because Object is the base class of all types in Dart.

However, up until now you could also do this:

Object x = 42; int i = x; // valid, x *holds* an int value String s = x; // valid, but shouldn't be allowed

The assignments above are known as implicit downcasts, and are valid in Dart 2.8 and below.

While you would never write code like this intentionally, implicit downcasts can go undetected in your code.

Consider this example:

List<int> values = [1, 2, 3]; List<int> doubled = values.map((v) => v * 2); print(doubled); // TypeError: Instance of 'MappedListIterable<int, int>': type 'MappedListIterable<int, int>' is not a subtype of type 'List<int>'

It looks like this code should work, but it throws a runtime exception instead.

This happens because map is a method of the Iterable class, which is an ancestor class for List.

More precisely, the expression values.map((v) => v * 2) has type MappedListIterable<int, int>. This can be assigned to a variable of type List<int> because implicit downcasts are allowed.

But it shouldn't! To fix the runtime exception we have to write:

List<int> doubled = values.map((v) => v * 2).toList();

It's very easy to forget .toList() - I've done it many times!

Implicit downcasts in Dart 2.9

Luckily, implicit downcasts are no longer allowed in Dart 2.9:

List<int> values = [1, 2, 3]; List<int> doubled = values.map((v) => v * 2); // A value of type 'Iterable<int*>*' can't be assigned to a variable of type 'List<int>'

This code produces a compile error. The first example also is a no-go:

Object x = 42; int i = x; // A value of type 'Object' can't be assigned to a variable of type 'int' String s = x; // A value of type 'Object' can't be assigned to a variable of type 'String'

This is much better.

Disabling implicit downcasts in analysis_options.yaml

As of June 2020, Dart 2.9 hasn't made it to the stable channel yet.

If you're on Dart 2.8 or below, you can disable some of the old "dynamic" rules in your analysis_options.yaml file:

analyzer: strong-mode: implicit-casts: false implicit-dynamic: false

This will force you to use explicit downcasts:

Object x = 42; int i = x as int; // compiles, legit String s = x as String; // compiles, will throw runtime exception

With explicit downcasts, you're less likely to write the wrong code.

Likewise, the previous example with map and toList() will also generate a compile time error:

List<int> values = [1, 2, 3]; List<int> doubled = values.map((v) => v * 2); // A value of type 'Iterable<int>' can't be assigned to a variable of type 'List<int>'

Conclusion

Implicit downcasts are an unsafe feature of the Dart language. You should disable them in analysis_options.yaml. And with Dart 2.9, they're gone for good. 😎

Happy Coding!

Want more?

Support my work and fast-track your Flutter learning with my in-depth courses.