Home > front end >  Downcasting generic of Future in dart
Downcasting generic of Future in dart

Time:03-18

I have a future that has a generic parameter, which is a superclass (A) of another class (B extends A). I know for a fact that the instance of the value of the Future is of the subtype. Why can't I downcast the Future<A> to Future<B> in dart? If I unwrap the Future once and then wrap it again using async/await, it works.

Here's an example:

class A {}
class B extends A{}

void main() {
  Future<A> getFuture() async { return B();}
  Future<B> getBasA() { return getFuture() as Future<B>;}
  Future<B> getBasAasync() async { return (await getFuture()) as B;}

  print(getBasAasync()); // Works
  print(getBasA()); // Throws at runtime
}

For the curious and as a motivation for the question, here's a closer-to-world example. I have a stream that emits data packets, which I filter and then get the first like this:

Future<T> getResponse<T extends ReceivedPacket>() =>
  getStream<ReceivedPacket>().firstWhere((packet) => packet is T) as Future<T>; //throws

Future<T> getResponse<T extends ReceivedPacket>() async { //works
  return (await  getStream<ReceivedPacket>().firstWhere((packet) => packet is T)) as T;
} 

PS: I've tried it out in Typescript (will happily compile and run) and C# (won't compile, but I have very limited C# knowledge). I understand that the answer to this question might be "because this is how the dart type system works". I'm just confused, because I'd have expected it either to fail at compile time like C# or work at runtime, too, like typescript.

CodePudding user response:

You declared getFuture() as returning Future<A> but with the async keyword, so Dart automatically transforms return B(); to (essentially) return Future<A>.value(B());. The returned Future was never a Future<B> and therefore cannot be downcast to it.

Creating the Future explicitly would do what you expect:

Future<A> getFuture() { return Future<B>.value(B()); }

You could argue that Dart when transforms return x; in async functions, it should create a Future<T> where T is the static type of x instead of inferring it from the function's return type. As lrn explained in comments, that can't be done because you might have:

class C extends A {}

Future<A> getFuture() async {
  await Future.delayed(const Duration(seconds: 1));

  if (Random().nextBool()) {
    return B();
  } else {
    return C();
  }
}

The caller must get a Future back immediately, but it won't be known whether its value will be a B or C until the Future eventually completes.

I'd have expected it either to fail at compile time like C#

I too have very limited experience with C#, but I think that C# gives you a compilation error because C# does not consider Generic<SubType> to be a subtype of Generic<SuperType> whereas Dart does.

  • Related