286: Creating Iterators with from_fn()
Tutorial Video
Text description (accessibility)
This video demonstrates the "286: Creating Iterators with from_fn()" functional Rust example. Difficulty level: Intermediate. Key concepts covered: Functional Programming. Building a custom iterator by defining a struct and implementing `Iterator` is powerful but verbose for simple cases. Key difference from OCaml: 1. **State style**: Rust's `from_fn` uses captured mutable variables (mutable closure captures); OCaml's `unfold` passes state as a pure value through each step.
Tutorial
The Problem
Building a custom iterator by defining a struct and implementing Iterator is powerful but verbose for simple cases. std::iter::from_fn() provides a lightweight alternative: create an iterator directly from a closure returning Option<T>. The closure captures its own mutable state, and each call produces the next element or None to terminate. This is the functional approach to generators — describing iteration as a stateful function.
🎯 Learning Outcomes
from_fn(f) as creating an iterator from a FnMut() -> Option<T> closurefrom_fn as the bridge between stateful computation and the iterator ecosystemCode Example
let mut n = 0i32;
let counter = std::iter::from_fn(move || {
n += 1;
if n <= 5 { Some(n) } else { None }
});Key Differences
from_fn uses captured mutable variables (mutable closure captures); OCaml's unfold passes state as a pure value through each step.unfold is purely functional (state is passed explicitly); Rust's from_fn is imperative (state is mutated in place).None/None to signal exhaustion; checked_add in Rust enables overflow-safe termination.from_fn iterators get all Iterator adapters, Seq.unfold gets all Seq functions.OCaml Approach
OCaml's Seq.unfold is the direct equivalent: it takes an initial state and a function state -> (element * state) option:
let counter max =
Seq.unfold (fun n -> if n > max then None else Some (n, n+1)) 1
let fibonacci =
Seq.unfold (fun (a, b) -> Some (a, (b, a+b))) (0, 1)
Seq.unfold and from_fn are semantically equivalent — both produce lazy sequences from stateful generators.
Full Source
#![allow(clippy::all)]
//! # Creating Iterators with from_fn()
//!
//! `std::iter::from_fn(f)` creates an iterator from a closure returning `Option<T>`.
/// Create a simple counting iterator using from_fn
pub fn counter(max: i32) -> impl Iterator<Item = i32> {
let mut n = 0;
std::iter::from_fn(move || {
n += 1;
if n <= max {
Some(n)
} else {
None
}
})
}
/// Create a Fibonacci iterator using from_fn
pub fn fibonacci() -> impl Iterator<Item = u64> {
let (mut a, mut b) = (0u64, 1u64);
std::iter::from_fn(move || {
let val = a;
let next = a.checked_add(b)?; // Return None on overflow
a = b;
b = next;
Some(val)
})
}
/// Parse numbers from whitespace-separated string
pub fn parse_numbers(input: &str) -> impl Iterator<Item = u32> + '_ {
let mut words = input.split_whitespace();
std::iter::from_fn(move || {
loop {
match words.next() {
None => return None,
Some(w) => {
if let Ok(n) = w.parse() {
return Some(n);
}
// skip invalid, continue to next word
}
}
}
})
}
/// Alternative: Create a range with custom step
pub fn stepped_range(start: i32, end: i32, step: i32) -> impl Iterator<Item = i32> {
let mut current = start;
std::iter::from_fn(move || {
if (step > 0 && current < end) || (step < 0 && current > end) {
let val = current;
current += step;
Some(val)
} else {
None
}
})
}
/// Create iterator from buffer simulation
pub fn buffer_reader(buffer: Vec<u8>) -> impl Iterator<Item = u8> {
let mut idx = 0;
std::iter::from_fn(move || {
if idx < buffer.len() {
let v = buffer[idx];
idx += 1;
Some(v)
} else {
None
}
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_counter() {
let result: Vec<i32> = counter(5).collect();
assert_eq!(result, vec![1, 2, 3, 4, 5]);
}
#[test]
fn test_counter_zero() {
let result: Vec<i32> = counter(0).collect();
assert!(result.is_empty());
}
#[test]
fn test_fibonacci_first_10() {
let result: Vec<u64> = fibonacci().take(10).collect();
assert_eq!(result, vec![0, 1, 1, 2, 3, 5, 8, 13, 21, 34]);
}
#[test]
fn test_parse_numbers() {
let result: Vec<u32> = parse_numbers("42 17 99 3 55").collect();
assert_eq!(result, vec![42, 17, 99, 3, 55]);
}
#[test]
fn test_parse_numbers_with_invalid() {
let result: Vec<u32> = parse_numbers("1 foo 3 bar 5").collect();
assert_eq!(result, vec![1, 3, 5]); // skips invalid
}
#[test]
fn test_stepped_range() {
let result: Vec<i32> = stepped_range(0, 10, 2).collect();
assert_eq!(result, vec![0, 2, 4, 6, 8]);
}
#[test]
fn test_stepped_range_negative() {
let result: Vec<i32> = stepped_range(10, 0, -3).collect();
assert_eq!(result, vec![10, 7, 4, 1]);
}
#[test]
fn test_buffer_reader() {
let result: Vec<u8> = buffer_reader(vec![1, 2, 3, 4, 5]).collect();
assert_eq!(result, vec![1, 2, 3, 4, 5]);
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_counter() {
let result: Vec<i32> = counter(5).collect();
assert_eq!(result, vec![1, 2, 3, 4, 5]);
}
#[test]
fn test_counter_zero() {
let result: Vec<i32> = counter(0).collect();
assert!(result.is_empty());
}
#[test]
fn test_fibonacci_first_10() {
let result: Vec<u64> = fibonacci().take(10).collect();
assert_eq!(result, vec![0, 1, 1, 2, 3, 5, 8, 13, 21, 34]);
}
#[test]
fn test_parse_numbers() {
let result: Vec<u32> = parse_numbers("42 17 99 3 55").collect();
assert_eq!(result, vec![42, 17, 99, 3, 55]);
}
#[test]
fn test_parse_numbers_with_invalid() {
let result: Vec<u32> = parse_numbers("1 foo 3 bar 5").collect();
assert_eq!(result, vec![1, 3, 5]); // skips invalid
}
#[test]
fn test_stepped_range() {
let result: Vec<i32> = stepped_range(0, 10, 2).collect();
assert_eq!(result, vec![0, 2, 4, 6, 8]);
}
#[test]
fn test_stepped_range_negative() {
let result: Vec<i32> = stepped_range(10, 0, -3).collect();
assert_eq!(result, vec![10, 7, 4, 1]);
}
#[test]
fn test_buffer_reader() {
let result: Vec<u8> = buffer_reader(vec![1, 2, 3, 4, 5]).collect();
assert_eq!(result, vec![1, 2, 3, 4, 5]);
}
}
Deep Comparison
OCaml vs Rust: Iterator from_fn
Pattern 1: Counter with Mutable State
OCaml
let counter =
let n = ref 0 in
Seq.unfold (fun () ->
if !n >= 5 then None
else begin incr n; Some (!n, ()) end
) ()
Rust
let mut n = 0i32;
let counter = std::iter::from_fn(move || {
n += 1;
if n <= 5 { Some(n) } else { None }
});
Pattern 2: Fibonacci Sequence
OCaml
let fib =
let a = ref 0 and b = ref 1 in
Seq.forever (fun () ->
let v = !a in
let next = !a + !b in
a := !b; b := next; v
)
Rust
let fib = {
let (mut a, mut b) = (0u64, 1u64);
std::iter::from_fn(move || {
let val = a;
let next = a + b;
a = b;
b = next;
Some(val)
})
};
Pattern 3: Parsing Tokens
Rust
let mut words = input.split_whitespace();
let numbers = std::iter::from_fn(|| {
words.next().and_then(|w| w.parse().ok())
});
Key Differences
| Aspect | OCaml | Rust |
|---|---|---|
| Constructor | Seq.unfold / Seq.of_dispenser | std::iter::from_fn |
| State | ref cells in closure | move closure captures |
| Termination | Return None | Return None |
| Infinite | Seq.forever | Always return Some, use .take(n) |
| Use case | General unfold pattern | Stateful generator without struct |
Exercises
from_fn to implement a random number generator iterator that produces pseudo-random numbers using an LCG (linear congruential generator) with captured seed state.from_fn, terminating when the value reaches 1.retry_until_success iterator using from_fn that calls a fallible function and keeps retrying until it returns Ok, yielding the attempts.