Introduction
Iterator provides a lazy, composable, and allocation-friendly way to process sequences in Silex smart contracts.
Use it when you want to:
- Transform values (
map,filter) - Combine streams (
chain,zip,flatten) - Consume results (
collect,sum,count,fold)
Most methods return a new Iterator, which lets you build pipelines without materializing intermediate arrays after every step.
This matters in smart contracts because each extra allocation and loop has a gas cost. Iterators let you describe a sequence of operations first, then consume it once at the end.
Why it matters
Without iterators, sequence processing often becomes:
- Create an array
- Loop manually
- Push transformed values into another array
- Loop again to filter or aggregate
That approach is verbose and usually allocates more than necessary. Iterator gives you a more direct model:
let result = values
.iter()
.filter(|v: u64| { return v > 0 })
.map(|v: u64| { return v * 2 })
.take(10)
.collect()This is easier to read and makes it clear that the data is being processed as a pipeline.
How it works
An iterator is a sequence that yields items one by one through next().
next() ⟶ optional<T0>If there is a next value, next() returns it. Otherwise it returns null.
Most iterator methods fall into two categories:
-
Adapters: return another iterator. Examples:
map,filter,skip,take,chain,zip,enumerate,rev,flatten -
Consumers: exhaust the iterator and produce a final result. Examples:
collect,sum,count,find,any,all,fold,position,for_each
This distinction is important: once an iterator is consumed, you should treat it as exhausted.
Construction patterns
Iterators are usually produced from other collections, but the type also provides constructors for common cases:
Iterator::once(value)for a single-element streamIterator::empty()for an empty streamIterator::unfold(seed, f)for stateful generation
API Overview
next() ⟶ optional<T0>
count() ⟶ u32
skip(n: u32) ⟶ Iterator
take(n: u32) ⟶ Iterator
chain(other: Iterator) ⟶ Iterator
enumerate() ⟶ Iterator
rev() ⟶ Iterator
collect() ⟶ T0[]
zip(other: Iterator) ⟶ Iterator
sum() ⟶ T0
map(mapper: closure(T0) -> T1) ⟶ Iterator
filter(predicate: closure(T0) -> bool) ⟶ Iterator
for_each(f: closure(T0))
find(predicate: closure(T0) -> bool) ⟶ optional<T0>
any(predicate: closure(T0) -> bool) ⟶ bool
all(predicate: closure(T0) -> bool) ⟶ bool
fold(init: T0, f: closure(T0, T0) -> T0) ⟶ T0
position(predicate: closure(T0) -> bool) ⟶ optional<u32>
Iterator::once(value: T0) ⟶ Iterator
Iterator::empty() ⟶ Iterator
Iterator::unfold(seed: T0, f: closure(T0) -> optional<(T1, T0)>) ⟶ Iterator
flatten() ⟶ IteratorAdvantages
- Lower intermediate allocation pressure: compose transformations before collecting.
- Readable data pipelines: the sequence of operations is explicit and linear.
- Built-in short-circuiting:
find,any,all, andpositioncan stop early. - Easy batching:
take,skip, andchainmake pagination and chunking straightforward. - Stateful generation:
unfoldlets you generate values without a prebuilt array.
Examples
Build from a single value or empty stream
let single: Iterator = Iterator::once(42)
let none: Iterator = Iterator::empty()Transform and filter
Double each value, keep only those above a threshold, then materialize the result.
let values: u64[] = [1, 2, 3, 4, 5]
let out: u64[] = values
.iter()
.map(|v: u64| { return v * 2 })
.filter(|v: u64| { return v > 5 })
.collect()Combine iterators
Merge two sequences into one without creating a temporary array yourself.
let a: u64[] = [1, 2]
let b: u64[] = [3, 4]
let merged: u64[] = a.iter().chain(b.iter()).collect()Aggregate
Use consumers when you only need a final answer, not another sequence.
let values: u64[] = [10, 20, 30]
let total: u64 = values.iter().sum()
let cnt: u32 = values.iter().count()
let first_pos: optional<u32> = values.iter().position(|x: u64| { return x == 20 })Early-exit checks
These operations stop as soon as the answer is known.
let values: u64[] = [2, 4, 6, 7, 8]
let has_odd: bool = values.iter().any(|x: u64| { return x % 2 == 1 })
let all_positive: bool = values.iter().all(|x: u64| { return x > 0 })
let first_big: optional<u64> = values.iter().find(|x: u64| { return x >= 6 })Enumerate and zip
Attach indices or pair values from two streams.
let names: string[] = ["alice", "bob", "carol"]
let scores: u64[] = [10, 20, 30]
let indexed_names: (u32, string)[] = names.iter().enumerate().collect()
let paired: (string, u64)[] = names.iter().zip(scores.iter()).collect()Fold for custom aggregation
Use fold when sum() is not enough.
let values: u64[] = [1, 2, 3, 4]
let total: u64 = values.iter().fold(0, |acc: u64, x: u64| { return acc + x })Bounded processing
Use skip and take to page through data or cap work.
let values: u64[] = [10, 20, 30, 40, 50]
let page: u64[] = values.iter().skip(1).take(2).collect()Stateful generator with unfold
Generate a sequence from a seed without storing the whole progression in advance.
// Produces: 1, 2, 4, 8, 16
let powers: u64[] = Iterator::unfold(1, |state: u64| {
return state <= 16 ? (state, state * 2) : null
}).collect()Flatten nested sequences
Flatten an iterator of arrays into one stream of values.
let groups: u64[][] = [[1, 2], [3], [4, 5]]
let flat: u64[] = groups.iter().flatten().collect()Notes
next()returnsoptional<T0>and advances the iterator.- Methods like
for_each,count,sum,collect,find, andfoldconsume the iterator. - Adapters such as
mapandfilterare lazy; they do not do useful work until the iterator is consumed. - Use
take(n)to bound work and keep execution predictable. - Prefer consumer methods like
anyorfindover collecting into an array when you only need one answer.