List.cast<Type>() doesn't actually cast the list (in Dart 2)?

1,973 views
Skip to first unread message

Ryan Gonzalez

unread,
Apr 2, 2018, 8:26:58 PM4/2/18
to misc
This is something kind of weird I've run into in my Flutter app:


class Base {}
class Derived1 extends Base {}
class Derived2 extends Base {}

void main() {
  // Create a List<Derived1>.
  var derived1List = <Derived1>[new Derived1()];
  // Cast to a List<Base> (note that this assignment succeeds).
  List<Base> baseList = derived1List.cast<Base>();
  // Try adding a Derived2 to the list (fails!).
  baseList.add(new Derived2() as Base);
}


The add operation gives me:

Unhandled exception:
type 'Derived2' is not a subtype of type 'Derived1' of 'value' where
  Derived2 is from file:///home/ryan/stuff/Downloads/dart-sdk/x.dart
  Derived1 is from file:///home/ryan/stuff/Downloads/dart-sdk/x.dart

#0      List.add (dart:core-patch/dart:core/growable_array.dart:149)
#1      main (file:///home/ryan/stuff/Downloads/dart-sdk/x.dart:8:12)
#2      _startIsolate.<anonymous closure> (dart:isolate-patch/dart:isolate/isolate_patch.dart:279)
#3      _RawReceivePortImpl._handleMessage (dart:isolate-patch/dart:isolate/isolate_patch.dart:165)


This seems rather odd, since it kind of kills the point of List.cast<Type> *and* makes it so that you can have a generic type that isn't actually that type (since the assignment works, so according to the type system, the operation should be valid).

Tested with Dart 2.0.0-dev.44.0.

--
Ryan (ライアン)
Yoko Shimomura, ryo (supercell/EGOIST), Hiroyuki Sawano >> everyone else
https://refi64.com/

Bob Nystrom

unread,
Apr 2, 2018, 8:57:35 PM4/2/18
to General Dart Discussion
Oof, that is confusing. I believe the behavior here is deliberate, but unintuitive.

List.cast<T>() gives you back a list that is still backed by the original list. It's a live view into that list. Because your original list is a List<Derived1>, you can't add a Derived2 to it, even through the view given to you by List.cast<Base>().

On Mon, Apr 2, 2018 at 5:26 PM, Ryan Gonzalez <rym...@gmail.com> wrote:
This is something kind of weird I've run into in my Flutter app:


class Base {}
class Derived1 extends Base {}
class Derived2 extends Base {}

void main() {
  // Create a List<Derived1>.
  var derived1List = <Derived1>[new Derived1()];
  // Cast to a List<Base> (note that this assignment succeeds).
  List<Base> baseList = derived1List.cast<Base>();
  // Try adding a Derived2 to the list (fails!).
  baseList.add(new Derived2() as Base);
}


The add operation gives me:

Unhandled exception:
type 'Derived2' is not a subtype of type 'Derived1' of 'value' where
  Derived2 is from file:///home/ryan/stuff/Downloads/dart-sdk/x.dart
  Derived1 is from file:///home/ryan/stuff/Downloads/dart-sdk/x.dart

#0      List.add (dart:core-patch/dart:core/growable_array.dart:149)
#1      main (file:///home/ryan/stuff/Downloads/dart-sdk/x.dart:8:12)
#2      _startIsolate.<anonymous closure> (dart:isolate-patch/dart:isolate/isolate_patch.dart:279)
#3      _RawReceivePortImpl._handleMessage (dart:isolate-patch/dart:isolate/isolate_patch.dart:165)


This seems rather odd, since it kind of kills the point of List.cast<Type>

List.cast<T>() is only ever useful for downcasting a list.

The normal use case is that you have a list of some wide type (say num), but you happen to know that it only contains elements of some narrow type (say int). You can use cast<T>() to cast it down to that narrower type and then pass the result to code that expects a list whose static type is that narrow type.

For upcasting, you don't need .cast<T>() at all. You can just cast the original list:

void main() {
  // Create a List<Derived1>.
  var derived1List = <Derived1>[new Derived1()];
  // Cast to a List<Base> (note that this assignment succeeds).
  List<Base> baseList = derived1List;
  // Try adding a Derived2 to the list (fails!).
  baseList.add(new Derived2() as Base);
}

This is allowed because all generics are covariant in Dart.

*and* makes it so that you can have a generic type that isn't actually that type (since the assignment works, so according to the type system, the operation should be valid).

Yeah, the problem is that covariance isn't statically type-safe, as you can see in the above example. Dart addresses that by checking covariant operations at runtime, which is the exception you're seeing. (For comparison, Java and C# treat arrays as covariant. Dart does the same for all generics.)

Cheers!

– bob

Ryan Gonzalez

unread,
Apr 2, 2018, 9:28:21 PM4/2/18
to mi...@dartlang.org
Ah...what are the changes of getting a mapCast or similar that does something like this? It'd be a nice feature for the strong mode Dart world...

--
For more ways to connect visit https://www.dartlang.org/community
---
You received this message because you are subscribed to the Google Groups "Dart Misc" group.
To unsubscribe from this group and stop receiving emails from it, send an email to misc+uns...@dartlang.org.
To view this discussion on the web visit https://groups.google.com/a/dartlang.org/d/msgid/misc/CAF8T8Lyn%3DxiBVK3jEP%3Dv23CTObDCYA8_9Lo8a1J_kaPmwkRtUw%40mail.gmail.com.

Bob Nystrom

unread,
Apr 3, 2018, 12:03:32 PM4/3/18
to General Dart Discussion
The behavior you want is probably there, but I'm not sure what you mean by "like this". What code would you like to be able to write and what would you have it do?

– bob

Ryan Gonzalez

unread,
Apr 3, 2018, 6:28:11 PM4/3/18
to misc
By "like this", I mean something like:

derived1List.mapCast<Base>()
// which does:
derived1List.map((c) => c as Base)

Except it wouldn't give off analyzer errors about redundant casts and would be more readable.

tatumizer-v0.2

unread,
Apr 3, 2018, 6:36:24 PM4/3/18
to Dart Misc
Maybe you are looking for constructor List.from(anotherList)?

Bob Nystrom

unread,
Apr 3, 2018, 8:36:11 PM4/3/18
to General Dart Discussion
On Tue, Apr 3, 2018 at 3:27 PM, Ryan Gonzalez <rym...@gmail.com> wrote:
By "like this", I mean something like:

derived1List.mapCast<Base>()
// which does:
derived1List.map((c) => c as Base)

Except it wouldn't give off analyzer errors about redundant casts and would be more readable.

I'm probably missing something. derived1List is already a List<Base> as far as the static type system is concerned. List<Base> is a supertype of List<Derived1>. This code is fine:

expectListBase(List<Base> list) {}

void main() {
  var derived1List = <Derived1>[new Derived1()];
  expectListBase(derived1List);
}

You can just use a List<Derived1> directly anywhere a List<Base> is expected.

– bob
 

Ryan Gonzalez

unread,
Apr 3, 2018, 9:06:23 PM4/3/18
to mi...@dartlang.org

But then trying to add an item of type Derived2 still fails, since the runtime checked mode doesn't see things that way. 

I guess I could just use List.from as mentioned, since this whole thing was mostly just me misunderstanding what List.cast does, and I was expecting a method for this (kind of like x.toList() is a neater-ish way of writing List<T>.from(x)). 

Bob Nystrom

unread,
Apr 4, 2018, 12:03:29 PM4/4/18
to General Dart Discussion
That's always going to fail if you are trying to add the Derived2 to the original list since the original list is declared as List<Derived1>. If you could get a Derived2 in there, it would break the type system.
 

I guess I could just use List.from as mentioned, since this whole thing was mostly just me misunderstanding what List.cast does, and I was expecting a method for this (kind of like x.toList() is a neater-ish way of writing List<T>.from(x)). 

Ah, so you want a copy of the list, not a view into the original list. In that case, yes, new List<Base>.from(derived1List) is the way to go.

Cheers!

– bob

Günter Zöchbauer

unread,
Apr 7, 2018, 5:24:53 AM4/7/18
to Dart Misc
what's the difference between cast and retype?

Patrice Chalin

unread,
Apr 8, 2018, 7:18:38 AM4/8/18
to Dart Misc
On Saturday, April 7, 2018 at 5:24:53 AM UTC-4, Günter Zöchbauer wrote:
what's the difference between cast and retype?

 FYI, Bob will be addressing that exact question in Effective Dart; to track progress see https://github.com/dart-lang/site-www/issues/736.

Günter Zöchbauer

unread,
Apr 22, 2018, 10:00:21 AM4/22/18
to Dart Misc
A bit late. Thanks for the link anyway :)
Reply all
Reply to author
Forward
0 new messages