Custom Iterators
Zolo's for x in expr is driven by two traits from core::iter:
Iterator<T>— a stateful cursor;next(self) -> T?advances it. Returningnilsignals exhaustion.IntoIterator<T>— a container that can produce a fresh cursor viaiter(self).for x in containercallsiter()automatically.
Any type implementing either trait plugs into for with no extra ceremony.
Custom cursor with for, an IntoIterator container, for i, x enumerate,
and an adapter chain.
// Feature: Custom iterators
// Traits: `Iterator<T>` (cursor) and `IntoIterator<T>` (producer)
// Works on: VM, native, wasm-aot
// Note: adapter chains (.map/.filter/.sum/…) work on VM only.
// `Iter` (the adapter type returned by `iter()`) lives in std.
use std::Iter
// ── 1. Iterator<T> — direct cursor ──────────────────────────────────────────
//
// Implement `Iterator<T>` to make a struct driveable by `for x in`.
// The single required method is `next(self) -> T?`; returning `nil` signals
// the end of the sequence.
struct Countdown { n: int }
impl Iterator<int> for Countdown {
fn next(self) -> int? {
if self.n <= 0 { return nil }
self.n = self.n - 1
return self.n + 1
}
}
// ── 2. IntoIterator<T> — container that produces a cursor ───────────────────
//
// Implement `IntoIterator<T>` when the iterable thing is a *container*.
// `iter()` returns a fresh cursor; `for x in container` calls `iter()` first.
struct Bag { items: [int] }
impl IntoIterator<int> for Bag {
fn iter(self) -> Iter<int> { return Iter.from(self.items) }
}
fn main() {
// for loop over a direct Iterator
let c = Countdown { n: 3 }
for x in c { print(x) }
// expected: 3
// expected: 2
// expected: 1
// Manual sum via for loop (works on all backends)
var total = 0
let c2 = Countdown { n: 3 }
for x in c2 { total = total + x }
print(total)
// expected: 6
// IntoIterator container
let b = Bag { items: [10, 20, 30] }
for x in b { print(x) }
// expected: 10
// expected: 20
// expected: 30
// for i, x enumerate (0-based index)
let arr = [100, 200, 300]
for i, x in arr { print(i) print(x) }
// expected: 0
// expected: 100
// expected: 1
// expected: 200
// expected: 2
// expected: 300
// Adapter chain on custom iterator — VM only
// (on native/wasm-aot use the for-loop form above)
let c3 = Countdown { n: 5 }
let mapped_sum = c3.map(|x| x * 2).filter(|x| x > 4).sum()
print(mapped_sum)
// expected: 24
}
Iterator — direct cursor
struct Countdown { n: int }
impl Iterator<int> for Countdown {
fn next(self) -> int? {
if self.n <= 0 { return nil }
self.n = self.n - 1
return self.n + 1
}
}
fn main() {
let c = Countdown { n: 3 }
for x in c { print(x) } // 3, 2, 1
}
IntoIterator — container
struct Bag { items: [int] }
impl IntoIterator<int> for Bag {
fn iter(self) -> Iter<int> { return Iter.from(self.items) }
}
fn main() {
let b = Bag { items: [1, 2, 3] }
for x in b { print(x) } // 1, 2, 3
}
for x in b desugars to calling b.iter() and driving the returned cursor.
for i, x — enumerate (0-based)
let arr = [10, 20, 30]
for i, x in arr { print(i) print(x) }
// 0 10 1 20 2 30
Works on arrays, IntoIterator containers, and map literals (where i becomes
the key and x the value).
Adapter chains — VM only
On the VM, a user iterator can be wrapped into the prelude Iter<T> adapter,
giving access to the full map/filter/take/fold/sum/collect/… suite:
let result = Countdown { n: 5 }
.map(|x| x * 2)
.filter(|x| x > 4)
.sum()
// 24
Backend limitation: adapter chains (.map().filter().sum()) and
for k, v in mapLiteral destructuring work on the VM only. On native
(Cranelift) and wasm-aot these backends lack the Lua Iter.* adapter runtime.
Use the for-loop form when you need cross-backend portability.
Generic bounds
A function can accept any Iterator<T> via a generic bound:
fn sum_all<I: Iterator<int>>(it: I) -> int {
var total = 0
for x in it { total = total + x }
return total
}
Iterator is a known trait; the bound is accepted by the compiler.
Runtime limitation — use
forinside generic bodies. Calling adapter methods (.fold/.map/…) directly on a genericI: Iteratorvalue fails at runtime on every backend. The generic parameteritis a user struct, not anIterwrapper, sorawgetfinds no adapter slot on it. Always use afor x in it { … }loop inside a generic function body.
Note: element-type enforcement across call sites (verifying I is specifically
Iterator<int> and not Iterator<str>) is a known follow-up — the compiler
currently stores the type arg but does not yet cross-check it at instantiation.
The nil end-sentinel
next() returns T? — a nullable. The end-of-sequence sentinel is nil.
This means a sequence whose legitimate elements can be nil cannot be
iterated using this protocol. Design such sequences with a wrapper type instead.
Errors
| Code | Meaning |
|---|---|
| TE828 | for x in expr where expr's type implements neither Iterator nor IntoIterator. |