Guide 16 of 53
Generators
Generator functions with fn* and yield for lazy sequences.
On this page
Generators are functions that can pause their execution and produce a sequence of values lazily. They are defined with fn* and use the yield keyword to emit values one at a time.
Basic Generator¶
fn* count_up(start: int, end: int) {
let mut i = start
while i <= end {
yield i
i = i + 1
}
}
for n in count_up(1, 5) {
print(n)
}
// 1
// 2
// 3
// 4
// 5Delegating a sequence¶
Use yield ... to forward the values of another iterator:
fn* middle() -> int {
yield 2
yield 3
}
fn* numbers() -> int {
yield 1
yield ... middle()
yield 4
}
for value in numbers() { print(value) }
// 1, 2, 3, 4Delegation acquires the child once when execution reaches that statement. It requests one child value for each pull of the parent, then continues the parent when the child is exhausted. It does not build an intermediate array. A delegated value must have a checked iterator contract; a scalar or an erased value reports TE198.
An owned nominal cursor needs the canonical Iterator and Cleanup
witnesses and a checked construction. A borrowed cursor parameter does not
prove that ownership merely by declaring an iterator type.
The nominal bridge currently accepts non-generic next(self) and
close(self) implementations.
The parent owns the delegated cursor. Exhaustion, an early close, a loop
exit or an ordinary failure closes the child and runs cleanup before
closing the parent. Closing an already closed cursor is harmless.
A delegated iterator can yield nil as an item when its element type
permits it; ordinary yield nil retains the existing end-of-sequence rule.
The spread must follow yield directly. yield [1, ... values] still
yields one array; yield (... values) is not delegation.
JavaScript and standalone wasm-aot report TE198 for delegated yields. Ordinary simple generators remain supported by wasm-aot.
Infinite Generators¶
Generators can be infinite — they produce values on demand:
fn* naturals() {
let mut n = 0
loop {
yield n
n = n + 1
}
}
fn* fibonacci() {
let mut a = 0
let mut b = 1
loop {
yield a
let next = a + b
a = b
b = next
}
}Use Iter.take to consume a finite number of values from an infinite generator:
use std::Iter
let first10 = fibonacci()
|> Iter.take(10)
|> Iter.collect()
print(first10)
// [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]Generator with Parameters¶
fn* range(start: int, end: int, step: int) {
let mut i = start
while i < end {
yield i
i = i + step
}
}
// Every 3rd number from 0 to 20
for n in range(0, 20, 3) {
print(n)
}
// 0, 3, 6, 9, 12, 15, 18Using Generators with Iter¶
Generators implement the Iter protocol, so all iterator combinators work:
use std::Iter
fn* squares() {
let mut n = 0
loop {
yield n * n
n = n + 1
}
}
// First 5 even squares
let result = squares()
|> Iter.filter(|x| x % 2 == 0)
|> Iter.take(5)
|> Iter.collect()
print(result) // [0, 4, 16, 36, 64]Early Return from Generator¶
A generator can return early, stopping the sequence:
fn* bounded_fib(max: int) {
let mut a = 0
let mut b = 1
while a <= max {
yield a
let next = a + b
a = b
b = next
}
// generator ends here
}
for n in bounded_fib(100) {
print(n)
}
// 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89Yield with Expressions¶
yield can return any expression:
fn* enumerate(items: [str]) {
let mut i = 0
for item in items {
yield (i, item)
i = i + 1
}
}
for (i, name) in enumerate(["Alice", "Bob", "Carol"]) {
print("{i}: {name}")
}
// 0: Alice
// 1: Bob
// 2: CarolGenerator vs Iter.from_fn¶
For simple cases you can also use Iter.from_fn with a closure that returns the next value:
use std::Iter
// Generator style
fn* counter() {
let mut n = 0
loop { yield n; n = n + 1 }
}
// Iter.from_fn style
fn counter2() {
let mut n = 0
return Iter.from_fn(|| {
let val = n
n = n + 1
return val
})
}Both produce identical lazy sequences. Generators are usually cleaner for complex stateful logic.
Collecting and Chaining¶
use std::Array
use std::Iter
fn* primes() {
let mut seen = []
let mut candidate = 2
loop {
let is_prime = seen
|> Array.all(|p| candidate % p != 0)
if is_prime {
yield candidate
seen = [...seen, candidate]
}
candidate = candidate + 1
}
}
let first_primes = primes()
|> Iter.take(10)
|> Iter.collect()
print(first_primes)
// [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]DOCS / FEEDBACK
Did this page leave a question?
Tell us where the explanation lost you. Documentation is part of the language experience.