Range Over Iterators in Dart

Starting with version 1.23, the language has added support for iterators, which lets us range over pretty much anything!

Let’s look at the List type from the previous example again. In that example, we had an AllElements method that returned a slice of all elements in the list. With iterators, we can do it better — as shown below.

class List<T> {
  Node<T>? head;
  Node<T>? tail;

  void push(T v) {
    if (tail == null) {
      head = Node(val: v);
      tail = head;
    } else {
      tail!.next = Node(val: v);
      tail = tail!.next;
    }
  }
}

class Node<T> {
  T val;
  Node<T>? next;

  Node({required this.val});
}

All returns an iterator, which in Dart is a Stream.

extension ListExtension<T> on List<T> {
  Stream<T> all() async* {
    Node<T>? current = head;
    while (current != null) {
      yield current.val;
      current = current.next;
    }
  }
}

Iteration doesn’t require an underlying data structure and doesn’t even have to be finite! Here’s a function returning an iterator over Fibonacci numbers: it keeps running as long as the consumer keeps listening.

Stream<int> genFib() async* {
  int a = 1, b = 1;
  while (true) {
    yield a;
    int temp = a;
    a = b;
    b = temp + b;
  }
}
void main() async {
  var lst = List<int>();
  lst.push(10);
  lst.push(13);
  lst.push(23);

  await for (var e in lst.all()) {
    print(e);
  }

  var all = await lst.all().toList();
  print("all: $all");

  await for (var n in genFib()) {
    if (n >= 10) break;
    print(n);
  }
}

Output:

10
13
23
all: [10, 13, 23]
1
1
2
3
5
8

Next example: Errors.