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!