#Iterators
Status: standard library specification draft.
This chapter defines the part of std.iter that lib/std writes in
ordinary hd over the language tier:
- the iterator adapters
filter,take,enumerate,map,fold, andcollect, with their laziness and ordering; - the adapters
skip,take_while,zip,chain, andflat_map, and the drainingany,all,find, andcount; - the
FromIteratortrait and the collectionscollectbuilds:List,Map,Result, andT?; maponList[T]and onT?.
The language tier keeps what the compiler knows by name (Iteration Protocols):
| Item | Why it stays in the language tier |
|---|---|
Iterator[T] |
the prelude cursor trait that for advances through next |
ListIter[T] and MapEntries[K, V] |
the cursor types of a list's and a map's iter, which Built-In Methods names |
Iterable[T] and its impls |
the lang item that drives for and comprehensions; List, Map, and the iterable ranges implement it, not Iterator |
Nothing in the language tier names an adapter, collect, or
FromIterator. collect's default target, List[T], is an ordinary
type-argument default,
which needs no compiler knowledge of collect.
#Collection Cursors
A list's iter returns a ListIter[T] and a map's a MapEntries[K, V],
as their Iterable implementations' IntoIter:
use std.iter.ListIter
fn evens(values: List[i32]) -> List[i32]:
let cursor: ListIter[i32] = values.iter()
cursor.filter(fn(value): value % 2 == 0).collect()- std-iter.cursor.list
std.iterdeclaresListIter[T], whosenextyields the list's elements in order, as the list held them wheniterran. - std-iter.cursor.map
std.iterdeclaresMapEntries[K < Eq & Hash, V], whosenextyields the map's entries as(K, V)tuples in the map's iteration order. - std-iter.cursor.privateBoth have private fields, so only
itermakes one, and both iterate a snapshot (Iterator Snapshots).
#Optional Cursors
Option iterates without building a list for its payload.
- std-iter.option.iterable-cursor
Option[T]implementsIterable[T], withstd.option.OptionIter[T]as itsIntoIter. - std-iter.option.cursor.someA
.Some(value)cursor yieldsvalueonce, then.Noneon every later call. - std-iter.option.cursor.noneA
.Nonecursor yields.Noneon every call. - std-iter.option.cursor.independentEach
itercall returns an independent cursor and leaves the optional unchanged.
#Iterator Factory
from_fn wraps a step closure in the concrete FromFn[T] cursor.
- std-iter.from-fn.value
std.iter.from_fn[T](step: fn() -> T?) -> FromFn[T]returns a cursor storing the closure. - std-iter.from-fn.every-nextEach
nextcall onFromFn[T]calls its stored closure, including calls after.None.
#Iterator Adapters
The prelude Iterator[T] also has iterator adapters: methods that wrap
an iterator in a new one, or drain it.
fn first_evens(values: List[i32]) -> List[(usize, i32)]:
values.iter().filter(fn(value): value % 2 == 0).enumerate().take(2).collect()| Rule | Method | Result |
|---|---|---|
filter |
fn filter(self, keep: fn(T) -> bool) -> Filter[Self, T] |
a new iterator over the items of self for which keep returns true |
take |
fn take(self, count: usize) -> Take[Self, T] |
a new iterator over the first count items of self, or fewer when self ends first |
enumerate |
fn enumerate(self) -> Enumerate[Self, T] |
a new iterator over (index, item) pairs, with usize indices counting from 0 |
map |
fn map[U](self, transform: fn(T) -> U) -> MapIter[Self, T, U] |
a new iterator over transform(item) for each item of self, in order |
std-iter.adapter.fold.valuefold |
fn fold[A, R](self, initial: A, step: fn(A, T) -> A $ R) -> A $ R |
the accumulator after step has combined it with each remaining item of self, in order, starting from initial |
std-iter.adapter.collect.value-defaultedcollect |
fn collect[C < FromIterator[T] = List[T]](self) -> C |
a C built from the remaining items of self, as What Collect Builds specifies |
-
std-iter.adapter.filter.valueThe
filterdefault method has signaturefn filter(self, keep: fn(T) -> bool) -> Filter[Self, T]. -
std-iter.adapter.take.valueThe
takedefault method has signaturefn take(self, count: usize) -> Take[Self, T]. -
std-iter.adapter.enumerate.valueThe
enumeratedefault method has signaturefn enumerate(self) -> Enumerate[Self, T]. -
std-iter.adapter.map.valueThe
mapdefault method has signaturefn map[U](self, transform: fn(T) -> U) -> MapIter[Self, T, U]. -
std-iter.adapter.skip.valueThe
skipdefault method has signaturefn skip(self, count: usize) -> Skip[Self, T]. -
std-iter.adapter.take-while.valueThe
take_whiledefault method has signaturefn take_while(self, keep: fn(T) -> bool) -> TakeWhile[Self, T]. -
std-iter.adapter.zip.valueThe
zipdefault method has signaturefn zip[U, J < Iterable[U]](self, other: J) -> Zip[Self, T, U]. -
std-iter.adapter.chain.valueThe
chaindefault method has signaturefn chain[J < Iterable[T]](self, other: J) -> Chain[Self, T]. -
std-iter.adapter.flat-map.owned-iterableThe
flat_mapdefault method has signaturefn flat_map[U, J < Iterable[U]](self, transform: fn(T) -> J) -> FlatMap[Self, T, U, J]. -
std-iter.adapter.trait-defaultsAdapters are default methods of the prelude
Iterator[T]trait and require no import. -
std-iter.adapter.lazyCalling
filter,take, orenumeratedoes not advanceself. -
std-iter.adapter.lazy.mapCalling
mapdoes not advanceselfeither. -
std-iter.adapter.lazy.stored-sourceAn adapter advances its stored source only when its own
nextis called. -
std-iter.adapter.take.limitThe iterator that
takereturns callsnextonselfat mostcounttimes. -
std-iter.adapter.fold.owned-drain
foldadvancesselfuntilnextreturns.None, which exhausts the owned cursor. -
std-iter.adapter.owned-sourceLazy adapters take
selfby value and store that source value in their returned concrete adapter. -
std-iter.adapter.mutable-reference-forwardingA mutable reference to an
I < Iterator[T]implementsIterator[T]by forwardingnextto the referenced cursor. -
std-iter.adapter.draining-valueDraining methods take
selfby value and advance their owned cursor; passing a mutable cursor reference advances the referenced cursor instead. -
std-iter.adapter.named-layoutEach adapter is a named generic struct holding its source and its callback or traversal state.
-
std-iter.adapter.dyn-bridge
DynIterator[T]is an ordinary concrete cursor storing an erased mutable iterator reference and forwardingnextto it. -
std-iter.adapter.dyn-forwardingInherent adapter and draining methods on
dyn Iterator[T]take&mut selfand delegate to aDynIterator[T]holding that reference. -
std-iter.adapter.callback-rowThe
keepcallback has the empty row. A function value whose row lists a requirement key does not fit it. Error:type-mismatch. -
std-iter.adapter.callback-row.mapThe
transformcallback ofmaphas the empty row too. -
std-iter.adapter.fold.rowThe
stepcallback offoldmay have a requirement rowR, andfoldthen requiresR. -
std-iter.adapter.callback-row.captureA
keeportransformclosure whose body uses a requirement key has that key in its row, even inside a$.withblock. So it does not fit. Error:type-mismatch.
use std.iter.FromFn
trait Logger
fn noisy(value: i32) -> bool $ Logger:
value > 0
fn positives(values: List[i32]) -> List[i32] $ Logger:
values.iter().filter(noisy).collect() # error: type-mismatch
fn scoped(values: List[i32], logger: *dyn Logger) -> List[i32]:
$.with(Logger=logger):
values.iter().filter(fn(value): noisy(value)).collect() # error: type-mismatch
fn drain(source: FromFn[i32]) -> List[i32]:
source.collect()A callback that needs a provider captures the provider value from $.use
outside the closure, so its row stays empty:
trait Logger:
fn level(self) -> i32
fn above_level(values: List[i32]) -> List[i32] $ Logger:
let logger: *dyn Logger = $.use(Logger)
values.iter().filter(fn(value): value > logger.level()).collect()Why. A lazy adapter's iterator calls
keeportransformfrom itsnext, whose row is empty, so a stored callback cannot wait for providers.foldcallsstepbefore it returns, so the row passes through.
#More Adapters
Iterator[T] also has adapters that skip, stop, pair, and join items,
and draining methods that search or count them:
fn body(lines: List[string]) -> List[string]:
lines.iter().skip(1).take_while(fn(line: string) -> bool: line != "").collect()
fn labeled(names: List[string]) -> List[(i32, string)]:
[1, 2, 3].iter().zip(names).collect()
fn has_blank(lines: List[string]) -> bool:
lines.iter().any(fn(line: string) -> bool: line.trim() == "")| Rule | Method | Result |
|---|---|---|
skip |
fn skip(self, count: usize) -> Skip[Self, T] |
a new iterator over the items of self after the first count |
take_while |
fn take_while(self, keep: fn(T) -> bool) -> TakeWhile[Self, T] |
a new iterator over the items of self before the first one for which keep returns false |
zip |
fn zip[U, J < Iterable[U]](self, other: J) -> Zip[Self, T, U] |
a new iterator over pairs of the items of self and other at the same position, which ends when either ends |
chain |
fn chain[J < Iterable[T]](self, other: J) -> Chain[Self, T] |
a new iterator over the items of self, then those of other |
flat_map |
fn flat_map[U, J < Iterable[U]](self, transform: fn(T) -> J) -> FlatMap[Self, T, U, J] |
a new iterator over the items of each iterable that transform(item) returns, in order |
std-iter.adapter.any.valueany |
fn any(self, test: fn(T) -> bool) -> bool |
whether test returns true for some remaining item of self |
std-iter.adapter.all.valueall |
fn all(self, test: fn(T) -> bool) -> bool |
whether test returns true for every remaining item of self |
std-iter.adapter.find.valuefind |
fn find(self, test: fn(T) -> bool) -> T? |
the first remaining item of self for which test returns true, or .None |
std-iter.adapter.count.valuecount |
fn count(self) -> usize |
the number of remaining items of self |
std-iter.adapter.sum.numeric-targetsum |
fn sum[S < Sum[T]](self) -> S |
the target built from the remaining items |
-
std-iter.adapter.lazy.moreCalling
skip,take_while,zip,chain, orflat_mapadvances no iterator. The returned iterator pulls only when its ownnextis called. -
std-iter.adapter.skip.first-nextThe first
nextcall ofskip's iterator reads and drops up tocountitems ofselfbefore it reads the item it returns. -
std-iter.adapter.take-while.stop
take_while's iterator reads and drops the first item thatkeeprejects, and callsnextonselfno more after it. -
std-iter.adapter.iterable-arg
zipandchaincallother.iter()once, when they are called, and read the items ofotherfrom that iterator only. -
std-iter.adapter.iterable-arg.iterator-boundAn iterator argument without an
Iterable[T]implementation is an error, byflow.for.iterator.iterable-bound. Error:unsatisfied-trait-bound. -
std-iter.adapter.zip-iterable.order
zip's iterator callsnextonselffirst, and on the iterator ofotheronly whenselfgave an item. So whenselfends first,otherloses no item. -
std-iter.adapter.chain-iterable.order
chain's iterator callsnexton the iterator ofotheronly afterselfhas returned.None, and onselfno more after that. -
std-iter.adapter.stop-early.owned
any,all, andfindadvance their owned cursor only until an item decides the result, and leave its remaining items unread. -
std-iter.adapter.exhaustedOn an exhausted iterator,
anyisfalse,allistrue,findis.None, andcountis 0. -
std-iter.adapter.count.owned-drain
countadvancesselfuntilnextreturns.None, which exhausts the owned cursor. -
std-iter.adapter.callback-row.moreThe callbacks of these methods have the empty row, as
filter'skeepdoes. A function value whose row lists a requirement key does not fit. Error:type-mismatch. -
std-iter.sum.numeric-orderThe numeric
Sum[T]implementation starts atT::zero()and adds each remaining item in order. -
std-iter.sum.numeric-overflowAn integer numeric sum outside the range of
Tpanics, as+does. Panic:integer-overflow. -
std-iter.sum.owned-drainThe numeric sum implementation advances its owned cursor until
nextreturns.None. -
std-iter.sum.target-bound
sumrequires a result type implementingSum[T]. Error:unsatisfied-trait-bound. -
std-iter.adapter.flat-map.open-once
flat_mapcallsiter()once on each transformed result, only when that result is reached. -
std-iter.adapter.flat-map.drain-innerIt drains that cursor before transforming the next source item, skipping empty results.
fn joined(first: List[i32], rest: &mut dyn Iterator[i32]) -> List[i32]:
first.iter().chain(rest).collect() # error: unsatisfied-trait-bound
fn joined_eagerly(first: List[i32], rest: *mut dyn Iterator[i32]) -> List[i32]:
let tail: List[i32] = rest.collect()
first.iter().chain(tail).collect()Note. An iterator's remaining items pass as a list, collected first, as
joined_eagerlyshows. An iterator that may never end goes in the receiver position instead:rest.zip(first)pairs the same items in swapped order.
Note.
[1, 2, 3].iter().sum()is6, and[0.5, 0.25].iter().sum()is0.75. A list has the same method:std-collections.helper.sum.
Why. The names and the pulling order are Rust's, so
zipandtake_whiledrop the same items there and here.zipandchaintake anyIterable, as Rust's two take anyIntoIterator, so a list, set, or range passes directly.Iis a bounded type parameter, soother.iter()is dispatched statically, with no trait value.
#Borrowed Cursors
by_ref creates a value cursor that advances an existing cursor:
fn first_two[I < Iterator[i32]](source: *mut I) -> List[i32]:
source.by_ref().take(2).collect()- std-iter.by-ref.method
Iterator[T].by_ref(&mut self) -> ByRef[Self]returns a borrowed cursor. - std-iter.by-ref.layout
ByRef[I]stores one mutable reference to its source cursor. - std-iter.by-ref.nextFor
I < Iterator[T],ByRef[I]implementsIterator[T]by forwardingnextto that source. - std-iter.by-ref.progressAdapting or draining a
ByRefvalue advances the original cursor. - std-iter.by-ref.permissionCalling
by_refrequires a writable cursor place or a mutable reference. Error:not-writable.
#Numeric Aggregation
Sum[T] and Product[T] build an aggregate from cursor items.
- std-iter.sum.trait
Sum[T]declaresfn sum[I < Iterator[T]](items: I) -> Self. - std-iter.product.trait
Product[T]declaresfn product[I < Iterator[T]](items: I) -> Self. - std-iter.sum.numericEvery numeric
TimplementsSum[T]. - std-iter.product.numericEvery numeric
TimplementsProduct[T]. - std-iter.sum.method
Iterator[T]providesfn sum[S < Sum[T]](self) -> S. - std-iter.product.method
Iterator[T]providesfn product[P < Product[T]](self) -> P. - std-iter.aggregate.targetThe expected result type or an explicit type argument can determine the aggregate target.
- std-iter.aggregate.inferenceAn aggregate target has no type-argument default and follows ordinary bound inference.
- std-iter.aggregate.unsolvedAn unsolved aggregate target requires an expected type or an explicit type argument. Error:
cannot-infer-type. - std-iter.product.numeric-orderThe numeric
Product[T]implementation starts atT::one()and multiplies each remaining item in order. - std-iter.product.numeric-overflowAn integer numeric product outside the range of
Tpanics, as*does. Panic:integer-overflow. - std-iter.product.owned-drainThe numeric product implementation advances its owned cursor until
nextreturns.None. - std-iter.product.target-bound
productrequires a result type implementingProduct[T]. Error:unsatisfied-trait-bound.
fn total(values: List[i32]) -> i32:
values.iter().sum::[i32]()
fn multiplied(values: List[i32]) -> i32:
let result: i32 = values.iter().product()
result#What Collect Builds
collect builds the collection that the expected type names:
fn parse_port(text: string) -> Result[usize, string]:
.Ok(text.len())
fn index(names: List[string]) -> Map[string, usize]:
names.iter().map(fn(name): (name, name.len())).collect()
fn parse_all(lines: List[string]) -> Result[List[usize], string]:
lines.iter().map(parse_port).collect()
fn count(values: List[i32]) -> usize:
copied := values.iter().collect()
copied.len()The collection being built implements the std.iter trait
FromIterator[T]:
trait FromIterator[T]:
fn from_iter[I < Iterator[T]](items: I) -> Self- std-iter.collect.generic-owned-cursor
FromIterator[T].from_iter[I < Iterator[T]](items: I) -> Selfbuilds a collection from the owned cursor. - std-iter.prelude.from-iterator
std.iteralso declaresFromIterator, which is not a prelude name. Code imports it to implement or name it, as inuse std.iter.FromIterator, and acollectcall needs no import. - std-iter.collect.call
collectreturnsC::from_iter(self). - std-iter.collect.expected
Cis solved like any call-site type argument: from the expected type, or from an explicit list such ascollect::[Map[string, i32]](). - std-iter.collect.defaultWhen nothing determines
C, its declared defaultList[T]applies. - std-iter.collect.boundA
Cthat does not implementFromIterator[T]is an error. Error:unsatisfied-trait-bound.
The standard library implements FromIterator for these prelude types:
| Rule | Collection | Items | Result |
|---|---|---|---|
| std-iter.collect.listList | List[T] |
T |
every remaining item, in order |
| std-iter.collect.map-lastMap | Map[K, V] |
(K, V) |
one entry per pair; for an equal key the later value wins |
| std-iter.collect.resultAll results | Result[C, E], where C < FromIterator[T] |
Result[T, E] |
.Ok of the C collected from the .Ok payloads, or the first .Err |
| std-iter.collect.optionAll values | C?, where C < FromIterator[T] |
T? |
.Some of the C collected from the .Some payloads, or .None at the first .None |
- std-iter.collect.owned-drainCollecting into a
Listor aMapadvancesselfuntilnextreturns.None, which exhausts the owned cursor. - std-iter.collect.owned-stopCollecting into a
Resultor an optional stops at the first.Error.Noneand leaves the rest of the owned cursor unread. - std-iter.collect.map-keyA
Mapcollection needsK < Eq & Hash, as every map does.
fn total(values: List[i32]) -> i32:
let size: i32 = values.iter().collect() # error: unsatisfied-trait-bound
sizeWhy. The expected type already names the target, as Rust's
collectdoes. AMapbuilt bycollectagrees with a map literal and a map comprehension on equal keys.
Note. The standard library writes both of its
Mapimplementations with that bound, asimpl[K < Eq & Hash, V] FromIterator[(K, V)] for Map[K, V]andimpl[K < Eq & Hash, V] Iterable[(K, V)] for Map[K, V]. Neither leaves the key unbounded.
#List And Optional Map
std also gives List[T] and T? a map method:
| Receiver | Method |
|---|---|
List[T] |
map[U](self, transform: fn(T) -> U) -> List[U] |
T? |
map[U](self, transform: fn(T) -> U) -> U? |
- std-iter.method.map
List.mapand optionalmapare non-suspending and evaluate the transform in source order.
tests:
use std.testing.assert_equal
it("map doubles each item, in order"):
assert_equal([+3, 1, 2].map(fn(value: i32) -> i32: value * 2), [+6, 2, 4], reason="doubled")
it("optional map keeps .None"):
let absent: i32? = .None
assert_equal(absent.map(fn(value: i32) -> i32: value * 2), .None, reason="kept")See also: Built-In Methods, which lists the methods of the built-in types that stay in the language tier.