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.