089 — Lazy Sequences
Tutorial
The Problem
Create infinite lazy sequences in Rust using std::iter::from_fn and std::iter::successors — without custom structs. Generate natural numbers, Fibonacci numbers, and powers of two using closures that capture mutable state. Compare with OCaml's Seq thunk-based lazy sequences.
🎯 Learning Outcomes
std::iter::from_fn(move || …) to create a stateful iterator from a closurestd::iter::successors(seed, step) for sequences defined by recurrence.take(n) to safely bound infinite iteratorsmove is required — the closure must own its mutable statefrom_fn to OCaml's Seq.Cons(v, thunk) constructionfrom_fn (general), successors (recurrence), and a struct iteratorCode Example
#![allow(clippy::all)]
// 089: Lazy Sequences
#[cfg(test)]
mod tests {
#[test]
fn test_naturals() {
let mut n = 0i32;
let v: Vec<i32> = std::iter::from_fn(move || {
let v = n;
n += 1;
Some(v)
})
.take(5)
.collect();
assert_eq!(v, vec![0, 1, 2, 3, 4]);
}
#[test]
fn test_fibs() {
let v: Vec<u64> = std::iter::successors(Some((0u64, 1u64)), |&(a, b)| Some((b, a + b)))
.map(|(a, _)| a)
.take(8)
.collect();
assert_eq!(v, vec![0, 1, 1, 2, 3, 5, 8, 13]);
}
#[test]
fn test_powers() {
let mut exp = 0u32;
let v: Vec<u64> = std::iter::from_fn(move || {
if exp >= 4 {
None
} else {
let v = 1u64 << exp;
exp += 1;
Some(v)
}
})
.collect();
assert_eq!(v, vec![1, 2, 4, 8]);
}
}Key Differences
| Aspect | Rust | OCaml |
|---|---|---|
| Generator | from_fn(move \|\| …) | let rec aux () = Seq.Cons(…) |
| Recurrence | successors(seed, step) | let rec aux a b () = Seq.Cons(a, aux b (a+b)) |
| State | move captured mutable variable | Recursive argument or ref |
| Termination | Return None | Return Seq.Nil |
| Bounding | .take(n) | Seq.take n |
| Sharing | No sharing (new state per call) | Structural sharing via thunks |
from_fn and successors are the quickest way to create custom lazy sequences in Rust without defining a struct. Use successors for single-state recurrences (Fibonacci, powers), from_fn for more general stateful generation, and a struct iterator for complex multi-field state.
OCaml Approach
OCaml's Seq type requires explicit thunk construction: let rec aux n () = Seq.Cons(n, aux (n+1)). The from_fn equivalent uses a mutable ref cell and a Seq.Cons(v, aux) where aux is the recursive thunk. Seq.take n s materialises the first n elements. The key difference is that OCaml's sequences share structure via closures (not mutable state), while Rust's from_fn uses mutated captured variables.
Full Source
#![allow(clippy::all)]
// 089: Lazy Sequences
#[cfg(test)]
mod tests {
#[test]
fn test_naturals() {
let mut n = 0i32;
let v: Vec<i32> = std::iter::from_fn(move || {
let v = n;
n += 1;
Some(v)
})
.take(5)
.collect();
assert_eq!(v, vec![0, 1, 2, 3, 4]);
}
#[test]
fn test_fibs() {
let v: Vec<u64> = std::iter::successors(Some((0u64, 1u64)), |&(a, b)| Some((b, a + b)))
.map(|(a, _)| a)
.take(8)
.collect();
assert_eq!(v, vec![0, 1, 1, 2, 3, 5, 8, 13]);
}
#[test]
fn test_powers() {
let mut exp = 0u32;
let v: Vec<u64> = std::iter::from_fn(move || {
if exp >= 4 {
None
} else {
let v = 1u64 << exp;
exp += 1;
Some(v)
}
})
.collect();
assert_eq!(v, vec![1, 2, 4, 8]);
}
}#[cfg(test)]
mod tests {
#[test]
fn test_naturals() {
let mut n = 0i32;
let v: Vec<i32> = std::iter::from_fn(move || {
let v = n;
n += 1;
Some(v)
})
.take(5)
.collect();
assert_eq!(v, vec![0, 1, 2, 3, 4]);
}
#[test]
fn test_fibs() {
let v: Vec<u64> = std::iter::successors(Some((0u64, 1u64)), |&(a, b)| Some((b, a + b)))
.map(|(a, _)| a)
.take(8)
.collect();
assert_eq!(v, vec![0, 1, 1, 2, 3, 5, 8, 13]);
}
#[test]
fn test_powers() {
let mut exp = 0u32;
let v: Vec<u64> = std::iter::from_fn(move || {
if exp >= 4 {
None
} else {
let v = 1u64 << exp;
exp += 1;
Some(v)
}
})
.collect();
assert_eq!(v, vec![1, 2, 4, 8]);
}
}
Deep Comparison
Core Insight
Rust iterators are lazy by default — computation only happens when values are pulled
OCaml Approach
Rust Approach
Comparison Table
| Feature | OCaml | Rust |
|---|---|---|
| See | example.ml | example.rs |
Exercises
successors(Some(n), |&n| if n == 1 { None } else { Some(next) }).from_fn with a HashSet of composites.zip_lazy function that takes two from_fn-style sequences and yields pairs.std::iter::repeat_with to generate random numbers and compare it to from_fn with the same effect.Seq.