880-custom-iterator — Custom Iterator
Tutorial
The Problem
Standard ranges and slice iterators cover most use cases, but real programs require custom sequences: Fibonacci numbers, arithmetic progressions with arbitrary step sizes, geometric sequences, or domain-specific data streams. Implementing the Iterator trait for a custom struct allows these sequences to integrate fully with Rust's iterator ecosystem — all adapter methods (map, filter, take, chain) work automatically. This example shows two custom iterators: a stateful Fibonacci generator and a generic step range, both demonstrating how minimal trait implementation unlocks a rich API.
🎯 Learning Outcomes
SomeNone when exhausted.take() and .filter()Seq.unfold for generating custom sequencesCode Example
struct Fibonacci { a: u64, b: u64 }
impl Iterator for Fibonacci {
type Item = u64;
fn next(&mut self) -> Option<u64> {
let val = self.a;
let next = self.a + self.b;
self.a = self.b;
self.b = next;
Some(val)
}
}
let first10: Vec<u64> = Fibonacci::new().take(10).collect();Key Differences
next(&mut self); OCaml Seq.unfold threads state functionally.always-Some, OCaml via Seq.unfold with Some always.Seq.map/Seq.filter in OCaml).Seq values can be traversed multiple times (immutable).OCaml Approach
OCaml generates custom sequences with Seq.unfold: ('a -> ('b * 'a) option) -> 'a -> 'b Seq.t. The seed state is passed functionally; each step returns Some(value, next_state) or None. Fibonacci: Seq.unfold (fun (a, b) -> Some(a, (b, a+b))) (0, 1). Step range: Seq.unfold (fun i -> if i > n then None else Some(i, i + step)) start. The functional style avoids mutable struct fields; Rust uses mutable struct state within the Iterator implementation.
Full Source
#![allow(clippy::all)]
// Example 086: Custom Iterator — Fibonacci and Range with Step
// === Approach 1: Fibonacci iterator ===
struct Fibonacci {
a: u64,
b: u64,
}
impl Fibonacci {
fn new() -> Self {
Fibonacci { a: 0, b: 1 }
}
}
impl Iterator for Fibonacci {
type Item = u64;
fn next(&mut self) -> Option<u64> {
let val = self.a;
let next = self.a + self.b;
self.a = self.b;
self.b = next;
Some(val)
}
}
// === Approach 2: Range with step ===
struct StepRange<T> {
current: T,
end_: T,
step: T,
}
impl StepRange<i64> {
fn new(start: i64, end_: i64, step: i64) -> Self {
StepRange {
current: start,
end_,
step,
}
}
}
impl Iterator for StepRange<i64> {
type Item = i64;
fn next(&mut self) -> Option<i64> {
if self.current >= self.end_ {
None
} else {
let val = self.current;
self.current += self.step;
Some(val)
}
}
}
impl StepRange<f64> {
fn new_float(start: f64, end_: f64, step: f64) -> Self {
StepRange {
current: start,
end_,
step,
}
}
}
impl Iterator for StepRange<f64> {
type Item = f64;
fn next(&mut self) -> Option<f64> {
if self.current >= self.end_ {
None
} else {
let val = self.current;
self.current += self.step;
Some(val)
}
}
}
// === Approach 3: Collatz sequence iterator ===
struct Collatz {
current: u64,
done_: bool,
}
impl Collatz {
fn new(n: u64) -> Self {
Collatz {
current: n,
done_: false,
}
}
}
impl Iterator for Collatz {
type Item = u64;
fn next(&mut self) -> Option<u64> {
if self.done_ {
return None;
}
let val = self.current;
if val == 1 {
self.done_ = true;
} else if val % 2 == 0 {
self.current = val / 2;
} else {
self.current = 3 * val + 1;
}
Some(val)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fibonacci() {
let fibs: Vec<u64> = Fibonacci::new().take(10).collect();
assert_eq!(fibs, vec![0, 1, 1, 2, 3, 5, 8, 13, 21, 34]);
}
#[test]
fn test_fibonacci_sum() {
let sum: u64 = Fibonacci::new().take(10).sum();
assert_eq!(sum, 88);
}
#[test]
fn test_step_range() {
let v: Vec<i64> = StepRange::new(0, 10, 2).collect();
assert_eq!(v, vec![0, 2, 4, 6, 8]);
}
#[test]
fn test_step_range_by3() {
let v: Vec<i64> = StepRange::new(0, 10, 3).collect();
assert_eq!(v, vec![0, 3, 6, 9]);
}
#[test]
fn test_step_range_float() {
let v: Vec<f64> = StepRange::new_float(0.0, 1.0, 0.25).collect();
assert_eq!(v.len(), 4);
assert!((v[0] - 0.0).abs() < 1e-10);
assert!((v[3] - 0.75).abs() < 1e-10);
}
#[test]
fn test_collatz_6() {
let v: Vec<u64> = Collatz::new(6).collect();
assert_eq!(v, vec![6, 3, 10, 5, 16, 8, 4, 2, 1]);
}
#[test]
fn test_collatz_1() {
let v: Vec<u64> = Collatz::new(1).collect();
assert_eq!(v, vec![1]);
}
#[test]
fn test_collatz_length() {
let len = Collatz::new(27).count();
assert_eq!(len, 112);
}
#[test]
fn test_empty_range() {
let v: Vec<i64> = StepRange::new(10, 5, 1).collect();
assert!(v.is_empty());
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fibonacci() {
let fibs: Vec<u64> = Fibonacci::new().take(10).collect();
assert_eq!(fibs, vec![0, 1, 1, 2, 3, 5, 8, 13, 21, 34]);
}
#[test]
fn test_fibonacci_sum() {
let sum: u64 = Fibonacci::new().take(10).sum();
assert_eq!(sum, 88);
}
#[test]
fn test_step_range() {
let v: Vec<i64> = StepRange::new(0, 10, 2).collect();
assert_eq!(v, vec![0, 2, 4, 6, 8]);
}
#[test]
fn test_step_range_by3() {
let v: Vec<i64> = StepRange::new(0, 10, 3).collect();
assert_eq!(v, vec![0, 3, 6, 9]);
}
#[test]
fn test_step_range_float() {
let v: Vec<f64> = StepRange::new_float(0.0, 1.0, 0.25).collect();
assert_eq!(v.len(), 4);
assert!((v[0] - 0.0).abs() < 1e-10);
assert!((v[3] - 0.75).abs() < 1e-10);
}
#[test]
fn test_collatz_6() {
let v: Vec<u64> = Collatz::new(6).collect();
assert_eq!(v, vec![6, 3, 10, 5, 16, 8, 4, 2, 1]);
}
#[test]
fn test_collatz_1() {
let v: Vec<u64> = Collatz::new(1).collect();
assert_eq!(v, vec![1]);
}
#[test]
fn test_collatz_length() {
let len = Collatz::new(27).count();
assert_eq!(len, 112);
}
#[test]
fn test_empty_range() {
let v: Vec<i64> = StepRange::new(10, 5, 1).collect();
assert!(v.is_empty());
}
}
Deep Comparison
Comparison: Custom Iterator
Fibonacci
OCaml:
let fibonacci () =
let rec aux a b () =
Seq.Cons (a, aux b (a + b))
in
aux 0 1
let first10 = seq_take 10 (fibonacci ())
Rust:
struct Fibonacci { a: u64, b: u64 }
impl Iterator for Fibonacci {
type Item = u64;
fn next(&mut self) -> Option<u64> {
let val = self.a;
let next = self.a + self.b;
self.a = self.b;
self.b = next;
Some(val)
}
}
let first10: Vec<u64> = Fibonacci::new().take(10).collect();
Range with Step
OCaml:
let int_step_range start stop step =
{ current = start; stop; step; add = (+); compare }
Rust:
struct StepRange<T> { current: T, end_: T, step: T }
impl Iterator for StepRange<i64> {
type Item = i64;
fn next(&mut self) -> Option<i64> {
if self.current >= self.end_ { None }
else { let v = self.current; self.current += self.step; Some(v) }
}
}
Collatz Sequence
OCaml:
let collatz_list n =
let rec aux v acc =
if v = 1 then List.rev (1 :: acc)
else aux (if v mod 2 = 0 then v / 2 else 3 * v + 1) (v :: acc)
in aux n []
Rust:
struct Collatz { current: u64, done_: bool }
impl Iterator for Collatz {
type Item = u64;
fn next(&mut self) -> Option<u64> {
if self.done_ { return None; }
let val = self.current;
if val == 1 { self.done_ = true; }
else if val % 2 == 0 { self.current = val / 2; }
else { self.current = 3 * val + 1; }
Some(val)
}
}
Exercises
Collatz iterator that generates the Collatz sequence starting from a given number until it reaches 1.Geometric<T> iterator (geometric sequence: a, ar, ar², ...) that stops when the value exceeds a given limit.DoubleEndedIterator to StepRange<i64> so it can iterate from either end and support .rev().