Library
The iterator protocol — IIterable and IIterator
Two interfaces that make any type usable in a for-in loop, and the rules for which loop path the compiler picks.
since 0.1.0-alpha.1linuxwindowswasm
Description
Two interfaces in the prelude let any type take part in for (T x in e):
interface IIterator<T> { bool hasNext(); T next(); }
interface IIterable<T> { IIterator<T> iterator(); }
An IIterable<T> produces an IIterator<T>, and the iterator yields the elements: hasNext() says
whether another element is available, and next() returns it and moves on. To make your own type
iterable, implement IIterable<T> on it, and implement IIterator<T> on a small cursor class that
iterator() returns.
When the static type of e implements IIterable<T>, for (T x in e) body runs as
var it = e.iterator();
while (it.hasNext()) { T x = it.next(); body }
for (var x in e) takes the loop variable's type from the IIterable<T> it finds. break and
continue behave as in the hand-written while; continue goes back to hasNext(). A type that
is neither a built-in collection nor an IIterable<T> is a compile error that names the protocol.
A custom iterable used in for-in
class Countdown : IIterable<int> {
int from;
new Countdown(int n) { from = n; }
IIterator<int> iterator() => CountdownIterator(from);
}
class CountdownIterator : IIterator<int> {
int cur;
new CountdownIterator(int n) { cur = n; }
bool hasNext() => cur > 0;
int next() {
int v = cur;
cur = cur - 1;
return v;
}
}
for (int n in Countdown(3)) { console.writeln(n); }
for (var n in Countdown(5)) {
if (n == 4) { continue; }
if (n == 2) { break; }
console.writeln("n=${n}");
}
IIterator<int> it = Countdown(2).iterator();
while (it.hasNext()) { console.writeln(it.next()); }
3
2
1
n=5
n=3
2
1
Which path a loop takes
The compiler chooses the path for a for-in loop from the static type, never by testing at run
time:
- A range written as
a..bis a counted loop and creates no object. - An
Array,MaporRangevalue uses the built-in indexed fast path. - Any other type uses the protocol described above.
The built-in collections never go through the protocol for a for loop; that is why looping over
an array is fast. Array<T>, Map<K, V> and Range still implement IIterable, so an array or a
range can be passed where an IIterable<T> is expected, and its iterator() yields the same
sequence the loop would see (a Map yields Pair<K, V> entries in insertion order). A stream type
such as InStream<T> is neither a built-in collection nor a range, so a loop over a stream always
uses the protocol.
next() past the end is not specified by the protocol; callers are expected to test hasNext()
first. The iterators of the standard library throw RuntimeException in that case. Standard
iterators run over a snapshot of a pure value, so the collection can never change under them. An
iterator over a mutable object that you write yourself must handle that case itself.
A string is not iterable. Loop over s.chars(), which returns an array and makes the choice between
bytes and characters explicit.
Rules
for (T x in e)uses the protocol only wheneis not anArray,MaporRange.IIterable<T>has one method,iterator();IIterator<T>hashasNext()andnext().hasNext()must be safe to call repeatedly without moving the iterator.- The standard iterators throw
RuntimeExceptionwhennext()is called past the end. - A string has no iterator; use
chars().
Examples
Using iterators of the built-in collections
Array<int> xs = [1, 2];
IIterator<int> it = xs.iterator();
console.writeln(it.next());
console.writeln(it.next());
console.writeln(it.hasNext());
try {
it.next();
} catch (RuntimeException e) {
console.writeln("past the end");
}
void total(IIterable<int> src) {
IIterator<int> i = src.iterator();
int sum = 0;
while (i.hasNext()) { sum = sum + i.next(); }
console.writeln(sum);
}
total([4, 5]);
total(1..3);
1
2
false
past the end
9
6
Notes
Seq builds a lazy pipeline on top of this protocol; see the Seq entry.
See also
- IIterable — A source of values that can be walked with
for. - IIterator — The pull protocol for walking a sequence one value at a time.
- Range — A run of consecutive integers written
a..b, including both ends. - Array — An ordered sequence of values of one type,
Array<T>, with value semantics. - Map — An associative collection from keys of type
Kto values of typeV, with value semantics. - Seq — A lazy sequence: a pipeline of steps that does no work until its result is requested.