Why direct usage of generic value is not possible but same is possible if returned from method in Dart

123

The issue here is that the parameter of Future<T>.value has the type FutureOr<T>. It can be either a future or a value.

Also, Dart type inference works by "pushing down" a context type, then trying to make the expression work at that type, and finally pushing the final static type back up. If an expression like Future.value(...) has a context type, the missing type argument is always inferred from the context type.

When you write

  Future<Future<User>> fut2 = Future.value(Future.value(Student()));

the context type of the outer Future.value, the type we know it should have, is Future<Future<User>>. That makes its argument have context type FutureOr<Future<User>>. The argument is Future.value(Student()), where we don't yet know anything about Student() because we haven't gotten to it in the type inference yet, we're still working out way down towards it.

A Future<X> can satisfy that FutureOr<Future<User>> in two ways, either by being a Future<User> or by being a Future<Future<User>>. Type inference then guesses that it's the latter. It's wrong, but it can't see that yet. The way type inference works, it has to use the context type when there is one, but the context type is ambiguous, and it ends up choosing the wrong option.

You are hitting an edge case of the type inference where the context type can be satisfied in two different ways, and the downwards type inference chooses the wrong one. It's a good heuristic that if you have a FutureOr<...> context type, and you see a Future constructor, you want the Future-part of the FutureOr<...>. It breaks down when you have FutureOr<Future<...>>. So, don't do that!

My recommendation, in complete generality, is to never have a Future<Future<anything>> in your program. Not only does it avoid problems like this, but it's also a better model for your code. A future which eventually completes to something which eventually completes to a value ... just make it eventually complete to that value directly. Waiting for the intermediate future is just needless busywork.

Share:
123
manikanta
Author by

manikanta

Updated on January 01, 2023

Comments

  • manikanta
    manikanta over 1 year

    Why is it assigning a value to a generic field is not possible when assigned directly, but same is possible when using a variable reference or method return value (here, same value is assigned to the variable and method returns the same value)?

    class User {}
    
    class Teacher extends User {}
    
    class Student extends User {}
    
    Future<User> getUser() {
      return Future.value(Student());
    }
    
    void main() {
      Future<Future<User>> fut = Future.value(getUser()); // <----- No error
    
      Future<Future<User>> fut2 = Future.value(Future.value(Student())); // <----- Getting error
    
      Future<User> userFut3 = Future.value(Student());
      Future<Future<User>> fut3 = Future.value(userFut3); // <----- No error
    }
    

    Getting below error when Future.value(Future.value(Student())) assigned directly.

    Error: The argument type 'Student' can't be assigned to the parameter type 'FutureOr<Future<User>>?'.
    
  • manikanta
    manikanta over 2 years
    var userFut = Future.value(Student()); Future<Future<User>> fut = Future.value(userFut) is working, which means Dart is able to infer the type of Future.value(Student()) as Future<Student>. So not sure why Dart can't infer when passed as method argument. Can you please explain
  • manikanta
    manikanta over 2 years
    Very well explained. I had to read several times to just understand gist of this. I'm not really using Future<Future<T>>. I had other question and when trying to come up with minimal repro code, I came across this, and thought of improving my Dart knowledge. Thanks again for detailed explanation.