Skip to content

27 · Standard Library

Iterators (std::Iter)

Lazy sequences: range, map, filter, fold, take, skip, zip, collect and each.

On this page

std::Iter provides lazy pipelines — each step only processes an item when the next step requests it. No intermediate array is allocated until you call collect() or each().

Optional values and the end of iteration

Iter.from(array), map, filter and collect preserve optional elements. A callback returning nil from map produces a present item; it does not end the pipeline. A for loop visits that item and continues with the next one.

Iter.from_fn(callback) keeps its existing sentinel contract: nil returned by the source callback ends the sequence. The same convention applies to a custom iterator's legacy next() method. To process missing values, start from an array or produce them in a map after the source.

Range

Iter::range(start, end) produces the integers [start, end) without materialising the array. It is the most common source for a pipeline:

Exclusive numeric sequence at the end; collect() materialises the array.

01-range.zolo
Playground
// Feature: Iter.range — lazy numeric sequence

// When to use: producing a source for a pipeline without materializing an array.


use std::Iter

// Simple range 0..5 (end exclusive).

let r = Iter::range(0, 5)
print(r.collect())

// expected: [0, 1, 2, 3, 4]


// Empty range when start >= end.

let empty = Iter::range(5, 5)
print(empty.collect())

// expected: []


// Sum from 1 to 10.

let sum = Iter::range(1, 11).fold(0, |acc, x| acc + x)
print(sum)

// expected: 55


// Range feeding map.

let squares = Iter::range(1, 6).map(|x| x * x).collect()
print(squares)
// expected: [1, 4, 9, 16, 25]

Map and Filter

map transforms each item; filter discards those that do not satisfy the predicate. Chained together they form a linear-reading pipeline:

filter -> map -> collect with no intermediate arrays.

02-map-filter.zolo
Playground
// Feature: Iter.map / Iter.filter — lazy transform and filter

// When to use: processing pipelines without allocating intermediate arrays.


use std::Iter

// map — transforms each item.

let doubled = Iter::range(1, 6).map(|x| x * 2).collect()
print(doubled)

// expected: [2, 4, 6, 8, 10]


// filter — keeps items that pass the predicate.

let evens = Iter::range(1, 11).filter(|x| x % 2 == 0).collect()
print(evens)

// expected: [2, 4, 6, 8, 10]


// Pipeline: filter -> map -> collect.

let it = Iter::range(1, 11)
let only_even = it.filter(|x| x % 2 == 0)
let squared = only_even.map(|x| x * x)
print(squared.collect())
// expected: [4, 16, 36, 64, 100]

Fold

fold(acc, |acc, x| ...) reduces the whole sequence to a single value — sum, product, concatenated string:

Sum, factorial and sum of even squares with the same combinator.

03-fold.zolo
Playground
// Feature: Iter.fold — reduce an iterator to a single value

// When to use: summing, multiplying, building a final string, counting.


use std::Iter

// Sum 1..10.

let sum = Iter::range(1, 11).fold(0, |acc, x| acc + x)
print(sum)  // expected: 55


// Product 1..6 (= 5!).

let fact = Iter::range(1, 6).fold(1, |acc, x| acc * x)
print(fact)  // expected: 120


// Sum of even squares from 1..10.

let it = Iter::range(1, 11)
let it2 = it.map(|x| x * x)
let it3 = it2.filter(|x| x % 2 == 0)
let result = it3.fold(0, |acc, x| acc + x)
print(result)  // expected: 220


// String concatenation.

let nums = Iter::range(1, 4)
let joined = nums.fold("", |acc, x| acc + "{x};")
print(joined)
// expected: 1;2;3;

Take and Skip

take(n) limits how many items flow forward; skip(n) ignores the first ones. Together they implement pagination on any iterator:

skip(1).take(3) — skips the header and takes the next three lines.

04-take-skip.zolo
Playground
// Feature: Iter.take / Iter.skip — limit and skip elements

// When to use: pagination, taking first N, ignoring a stream header.


use std::Iter

// take — grabs the first N.

let first3 = Iter::range(0, 100).take(3).collect()
print(first3)

// expected: [0, 1, 2]


// skip — drops the first N.

let skip5 = Iter::range(0, 20).skip(5).take(4).collect()
print(skip5)

// expected: [5, 6, 7, 8]


// take beyond the length — stops at the end.

let small = Iter::range(0, 3).take(100).collect()
print(small)

// expected: [0, 1, 2]


// Skip the header of a "stream" (range simulating lines).

let lines = Iter::range(1, 11)  // 10 items

let body = lines.skip(1)  // ignore the "header"

let preview = body.take(3)
print(preview.collect())
// expected: [2, 3, 4]

Zip

zip combines two iterators into [a, b] pairs, stopping at the shorter one:

Value pairs and sum mapping with zip -> map.

05-zip.zolo
Playground
// Feature: Iter.zip — combines two iterators into pairs

// When to use: pairing index/value, headers with columns, etc.


use std::Iter

// Simple zip — stops at the shorter side.

let a = Iter::range(1, 4)
let b = Iter::range(10, 14)
let pairs = a.zip(b).collect()
// Each pair is an [x, y] array.

print(pairs.len())  // expected: 3

print(pairs[0])  // expected: [1, 10]

print(pairs[1])  // expected: [2, 11]

print(pairs[2])  // expected: [3, 12]


// Zip stops at the shorter side — 3 items, not 5.

let short = Iter::range(0, 3)
let long  = Iter::range(0, 5)
print(short.zip(long).collect().len())

// expected: 3


// Combine with map to sum pairs.

let xs = Iter::range(1, 5)
let ys = Iter::range(10, 14)
let zipped = xs.zip(ys)
let sums = zipped.map(|p| p[0] + p[1])
print(sums.collect())
// expected: [11, 13, 15, 17]

Collect and Each

collect() terminates the pipeline and returns an array. each() executes a side effect per item and returns nil — useful for printing or accumulating into an external variable:

collect returns an array; each is for effects — it returns no value.

06-collect-each.zolo
Playground
// Feature: Iter.collect / Iter.each — terminate a pipeline

// When to use: collect when you need the array; each for side effects.


use std::Iter

// collect — materializes the iterator into an array.

let arr = Iter::range(1, 5).collect()
print(arr)
// expected: [1, 2, 3, 4]

print(arr.len())  // expected: 4


// each — runs a side effect per item, returns nil.

print("--- each ---")
Iter::range(1, 4).each(|x| print("item={x}"))

// expected: --- each ---

// expected: item=1

// expected: item=2

// expected: item=3


// Full pipeline: range -> filter -> map -> collect.

let it1 = Iter::range(1, 11)
let it2 = it1.filter(|x| x % 2 == 1)  // odd numbers

let it3 = it2.map(|x| x * 10)
print(it3.collect())
// expected: [10, 30, 50, 70, 90]

Backend behavior

The wasm-aot backend still evaluates array-backed Iter.from pipelines eagerly. Optional slots are preserved there too. Its from_fn pipelines support map, filter, collect and for; unsupported adapters on that source fail explicitly. Full lazy-iterator parity remains separate work.

Challenge

Build a pipeline that produces the first 5 odd numbers greater than 10 using range, filter, take and collect. How many calls to filter are executed?

Global index

Find your way through Zolo

Try an idea

Start here

9 results

9 results

enespt-br