dartgenericsnull-safety

How to write a generic function that returns nullable only when it gets a nullable in Dart


The following function is an example that represents a more complex situation that can not be simplified:

String? intToString(int? i) {
  if (i == null) return null;
  return i.toString();
}

Can this function be rewritten using generics in a way that its return type is a String instead of a String? when the parameter is an int?

To make things clearer, here is an example where the nullability of the return type depends on the argument:

void main() {
  List<String> non_nullable = ['A', 'B'];
  print(first(non_nullable).length); // Does compile
  List<String?> nullable = ['A', 'B', null];
  print(first(nullable).length); // Fails to compile
}

T first<T>(List<T> ts) {
  return ts[0];
}

But can I do this when the parameter type and the return type are different?


Solution

  • You cannot. Nothing in the Dart type system allows two types to co-vary such that you get either String?-and-int? or String-and-int. You cannot abstract over nullability. (Or asynchrony, or any other monad-like type abstraction.)

    What's needed for that is higher order types, also called "modules" in some languages, which work like a function for types, so that you can apply the same type function (either the identity or applying ?) to the type. The Dart type system does not have higher order types in any way.

    If it did, something hypothetically like a generic type argument:

    M<String> first<M<T> extends T?>(M<int> value) => ...
    

    that's still unlikely to be able to work without some functionality that works on M<T> values in general, which is why modules both create types and have functions in them. (And maybe a typedef Nullable<T> = T?; would be a valid type argument to such a function. But really to make sense, it needs to also abstract over static operations.)