16

Let's say I had some function that takes a generic type as an argument. How do I check within that function whether the generic type argument is nullable or not? I want do something like this:

void func<T>() {
  print(T is nullable);
}

void main(){
  func<int>(); //prints false
  func<int?>(); //prints true
}

All I can think of to do is to check if T.toString() ends with a ? which is very hacky.

Jon Aird
  • 823
  • 1
  • 9
  • 18
  • I researched and found nothing... I think you could try to extend the basic built in object class with extension methods... – pedro pimont Feb 17 '21 at 11:33
  • 1
    I believe if you ask "is Object?", it'll be true only for nullable types. – Randal Schwartz Feb 17 '21 at 16:13
  • @RandalSchwartz `T is Object?` won't work; `is` requires an instance. `T == Object?` is not legal syntax (and `==` wouldn't work for derived types anyway). `instance is Object?` also wouldn't work because it's always true. `instance is! Object` could sort of work, but I don't think that's any better than `instance != null`; it doesn't tell you anything about the parameterized type itself. – jamesdlin Feb 17 '21 at 19:01
  • @JonAird *Why* do you want such a check? Either you should make your generic accept `T?` and assume that it's nullable everywhere, or you should make your generic require non-nullable types by adding a `T extends Object` constraint. Also see https://github.com/dart-lang/language/issues/143. – jamesdlin Feb 17 '21 at 19:05
  • @jamesdlin for example when implementing ListMixin using the default list behavior. You must implement the `length` setter. If the new length is larger than the current length, the length of the list is extended with new elements being set to `null`. It's desirable for a list implementation to accept nullable or non-nullable types. – Jon Aird Feb 18 '21 at 09:13
  • I think this answer in another question is also valuable. https://stackoverflow.com/a/67448929/4873896 – Hyukjoong Kim May 25 '22 at 06:01

4 Answers4

37

Try:

bool isNullable<T>() => null is T;
lrn
  • 64,680
  • 7
  • 105
  • 121
3

The accepted answer really just checks if the type can be null. It doesn't care about the type that you are operating the null operator on.

If you want to check if a type is a specific nullable type, a.k.a if you want to check if a type is specifically one of type DateTime? and not String?, you can't do this in dart via T == DateTime? as this conflicts with ternary operator syntax.

However, since dart allows passing nullable types into generic arguments, it's possible to it like so:

bool isType<T, Y>() => T == Y;

isType<T, DateTime?>() works.

HJo
  • 1,902
  • 1
  • 19
  • 30
  • 1
    Naming such a function `isType` is potentially misleading since "is" might imply an "is-a" check instead an equality check. Naming it `equalsType` would be clearer, or perhaps make a more general `Type typeIdentity() => T` function and then do `T == typeIdentity()`. – jamesdlin Apr 05 '23 at 03:34
1

I created an extension method on type T to check for null.

extension _Generic<T extends Object?> on T? {
  bool get isNullable {
    try {
      // throws an exception if T is not nullable
      final value = null as T;
      return true;
    } catch (_) {
      return false;
    }
  }
}

Running these checks all pass

void main() {
  print('hello world'.isNullable); // false
  print(1.isNullable); // false
  print(1.0.isNullable); // false
  print(true.isNullable); // false
  print([].isNullable); // false
  print({}.isNullable); // false
  print(null.isNullable); // true
  print(null.runtimeType.isNullable); // false, because `runtimeType` is not nullable
}
mrgnhnt96
  • 3,562
  • 1
  • 17
  • 36
  • "And @Irn method works, except for when `T` is type `Type`". Huh? The *type* of `T` is always `Type`. Can you provide a specific example that demonstrates in which case this approach is necessary? Also, as written, your code won't work as a freestanding function; where does it get `T` from? (Finally, you mean *lrn* (with a lowercase L), not Irn. He's Lasse Nielsen, member of the Dart language team.) – jamesdlin Mar 30 '23 at 18:17
  • This is an extension method on generic type `T`, that's where it comes from. `T` at runtime is not of type `Type`, unless the type parameter is a the literal keyword `Type`. "Dart generic types are reified, which means that they carry their type information around at runtime" - dart docs for generics. To be honest, its been a while since I answered this, and I can't replicate an example to justify my answer. Sorry about @lrn's handle incorrect. l and I look the exact same, I'm not the first do this and I won't be the last. I'm sure he gets it all the time. I'm sorry for any possible confusion – mrgnhnt96 Mar 31 '23 at 12:36
  • Your original claim didn't make sense because the *type* of `T` should always be `Type`. That is, `T is Type` should be true (or [`T.staticType == Type`](https://pub.dev/documentation/dartbag/latest/debug/StaticTypeExtension/staticType.html) should be true, or `T.runtimeType` should be something like `Type` or `_Type`). The *value* of `T` usually will be something else (e.g. `int`, `String?`, etc.). – jamesdlin Mar 31 '23 at 15:20
1

It creates a new List instance to verify if it's type is nullable or not by using the is operator which supports inheritance:

bool isNullable<T>() => <T?>[] is List<T>;
Alex Rintt
  • 1,618
  • 1
  • 11
  • 18
  • 1
    I don't see any advantage of this over [the `null is T` approach](https://stackoverflow.com/a/66249380/), but this is still a pretty clever trick that could be used more generally to check whether a generic's type argument derives from another one. – jamesdlin Jul 29 '22 at 04:56