Sdk: Null safety feedback: How to use firstWhere?

Created on 10 Jun 2020  路  14Comments  路  Source: dart-lang/sdk

I want to search a list and return null when the element is not found

void main() {
  var list = ['a', 'b', 'c'];

  String? d = list.firstWhere((e) => e == 'd', orElse: () => null);
}

https://nullsafety.dartpad.dev/2d0bc36ec1f3a5ade5550c0944702d73

With null safety, I get this error:

main.dart:4:62: Error: A value of type 'Null' can't be assigned to a variable of type 'String'.
  String? d = list.firstWhere((e) => e == 'd', orElse: () => null);

The orElse parameter of firstWhere is declared not nullable:

E firstWhere(bool test(E element), {E orElse()?})

Why not?

E? firstWhere(bool test(E element), {E? orElse()?})

What is the recommended way to search a collection with null safety enabled?

area-library library-core type-enhancement

Most helpful comment

Maybe the extension can be in the SDK itself then?

extension IterableExtension<E> on Iterable<E> {
  E? findFirst(bool Function(E) test) {}

  E? findLast(bool Function(E) test) {}

  E? findSingle(bool Function(E) test) {}
}

All 14 comments

My guess is that the return type of firstWhere are E and it would be confusing if it was changed to E? since the method are described with the following for the case of no element returning true in the test:

If no element satisfies test, the result of invoking the orElse function is returned. If orElse is omitted, it defaults to throwing a StateError.

A workaround (I hope there are better solutions...) you could do is to make an extension on Iterable like this:

void main() {
  var list = ['a', 'b', 'c'];

  String? d = list.firstWhereOrNull((e) => e == 'd');
  print(d); // null
}

extension FirstWhereOrNullExtension<E> on Iterable<E> {
  E? firstWhereOrNull(bool Function(E) test) {
    for (E element in this) {
      if (test(element)) return element;
    }
    return null;
  }
}

Alternative you could change the list to String? or cast it before calling firstWhere but these does not seem like good solutions from my perspective.

I am going to transfer this to dart-lang/language.

Duplicate of dart-lang/language#836

/cc @leafpetersen @eernstg @lrhn

What is the recommended way to search a collection with null safety enabled?

My gues is: return E if it exist or throw (NoSuchElementException) if it doesn't exist and if orElse was not provided.

This make sense. If you want to return null then just use the orElse callback. Dart is becoming non-nullable by default so "by default" we should not expect null values.

One way would be

List<X> list = ...; 
X? firstOrNull = list.cast<X?>.firstWhere((v) => test(v!), orElse: () => null);

In practice, I'd go for an extension method.

Maybe the extension can be in the SDK itself then?

extension IterableExtension<E> on Iterable<E> {
  E? findFirst(bool Function(E) test) {}

  E? findLast(bool Function(E) test) {}

  E? findSingle(bool Function(E) test) {}
}

Maybe the extension can be in the SDK itself then?

extension IterableExtension<E> on Iterable<E> {
  E? findFirst(bool Function(E) test) {}

  E? findLast(bool Function(E) test) {}

  E? findSingle(bool Function(E) test) {}
}

@lrhn can this be a option? Also Dart can take advantage of extension to create built-in helper functions like Kotlin does, it provide a bunche of extension right inside the language.

FWIW, this situation is troublesome for the migraiton tool too. See https://github.com/dart-lang/sdk/issues/42382

Note that a similar problem applies to singleWhere (@yjbanov ran into this today)

I've also run into this when migrating tests. I like the idea of the above extension methods and would be happy to see them in the core library if that's feasible.

It's not impossible. I was targeting them for package;collection (https://github.com/dart-lang/collection/pull/135), and I'm a little disinclined to add extension methods for something that really should be real methods in the library where the real methods would fit in.
(On the other hand, if we ever get interface default methods, I guess you would be able to upgrade extension methods to interface default methods)

I've used extension methods for the following, following convention with try:

  • tryFirst
  • tryLast
  • trySingle
  • tryFirstWhere
  • tryLastWhere
  • trySingleWhere
  • tryCast (instead of throwing TypeError with as, return null)

This makes the intent clear when reading code with these methods.

FWIW I'm also running into this when migrating protobufs.

Was this page helpful?
0 / 5 - 0 ratings