0

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?

3
  • You can't. The return type must be statically known, making the return type dependent on a different argument type would require evaluating a condition, and Dart's support for compile-time evaluation of constant expressions is very limited. Commented Dec 3, 2024 at 4:43
  • For that matter, even ignoring nullability, how would you expect a generic function to work where the argument type and return type are different, unless the generic function explicitly takes both types as type parameters? Commented Dec 3, 2024 at 4:45
  • 1
    What you probably want is not a generic function but to have String intToString(int) and String? intToString(int?) overloads. But alas, Dart currently does not support overloaded functions. You instead will have to name them differently. Commented Dec 3, 2024 at 4:48

2 Answers 2

1

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.)

Sign up to request clarification or add additional context in comments.

Comments

0

Dart is null safe so you can't have a function that sometimes returns a nullable but sometimes a non-nullable.

(Updated on 2024-12-03: Added code for clarity based on OP's newly provided example) In the code you provided, you can simply add a null-aware symbol (?) to accommodate such nullable scenario.

void main() {
  // Both the argument and return type now accept nullable types
  T? first<T>(List<T?> ts) {
    return ts[0];
  }
  // Null-aware (?) operator to inform the compiler that first() might return null
  List<String> non_nullable = ['A', 'B'];
  print(first(non_nullable)?.length ); // 1
  List<String?> nullable = ['A', 'B', null];
  print(first(nullable)?.length); // 1
}

2 Comments

But that's the idea of generics. To have a class or function work with different types.
It is not clear what you mean by "Both the argument and return type now accept nullable types". In Mr.Clear's function the argument type matches the return type. The function you show always returns T?, a nullable type.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.