Custom Iterators
Iterators are one of Rust's most powerful abstractions. While you've used many built-in iterators and combinators, creating your own custom iterators unlocks the full potential of Rust's iteration patterns. By implementing the Iterator trait, you can create lazy, composable sequences that integrate seamlessly with all of Rust's iterator methods.
The Iterator trait requires only one method: next(). This method returns Option<Self::Item>, yielding Some(value) for each element and None when the sequence is exhausted. The key to implementing custom iterators is managing internal state—tracking where you are in the sequence and knowing when to stop.
The Iterator Trait
pub trait Iterator {
type Item;
fn next(&mut self) -> Option<Self::Item>;
}Once you implement next(), you get all the iterator adapters (.map(), .filter(), .fold(), etc.) for free!
Example: A Counter Iterator
struct Counter {
current: u32,
max: u32,
}
impl Counter {
fn new(max: u32) -> Self {
Counter { current: 0, max }
}
}
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<Self::Item> {
if self.current < self.max {
let value = self.current;
self.current += 1;
Some(value)
} else {
None
}
}
}
// Usage
let counter = Counter::new(3);
let values: Vec<u32> = counter.collect();
assert_eq!(values, vec![0, 1, 2]);Your Task
Implement the following custom iterators:
1. Fibonacci Iterator
Create a Fibonacci struct that generates Fibonacci numbers indefinitely:
Fibonacci::new() -> Fibonacci- Create a new Fibonacci iterator starting with 0, 1- Yields: 0, 1, 1, 2, 3, 5, 8, 13, 21, ...
2. StepRange Iterator
Create a StepRange struct that iterates from start to end (exclusive) with a custom step:
StepRange::new(start: i32, end: i32, step: i32) -> StepRange- Handles positive steps (ascending) and negative steps (descending)
- Yields nothing if step would never reach end (e.g., start=0, end=10, step=-1)
3. Cycle Iterator
Create a CycleN<T> struct that cycles through a slice a fixed number of times:
CycleN::new(items: &[T], times: usize) -> CycleN<T>whereT: Clone- Yields each element in order, repeating the sequence
timestimes total
4. Collatz Iterator
Create a Collatz struct that generates the Collatz sequence starting from a given number:
Collatz::new(start: u64) -> Collatz- If n is even: next = n / 2
- If n is odd: next = 3n + 1
- Stops when it reaches 1 (yields 1 as the final element)
5. Windows Iterator
Create a Windows<T> struct that yields overlapping windows of a fixed size:
Windows::new(items: &[T], size: usize) -> Windows<T>whereT: Clone- Yields slices (as
Vec<T>) of lengthsizefrom the items - Each window overlaps with the previous by
size - 1elements
6. Unfold Iterator
Create an Unfold<T, F> struct that generates values from a state and function:
Unfold::new(initial: T, f: F) -> Unfold<T, F>whereF: FnMut(&T) -> Option<T>- The function takes the current state and returns
Some(next_state)orNoneto stop - Yields each state value (including the initial value)
Examples
// Fibonacci
let fib = Fibonacci::new();
let first_ten: Vec<u64> = fib.take(10).collect();
assert_eq!(
first_ten,
vec![0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
);
// StepRange
let range = StepRange::new(0, 10, 2);
let evens: Vec<i32> = range.collect();
assert_eq!(evens, vec![0, 2, 4, 6, 8]);
let countdown = StepRange::new(5, 0, -1);
let nums: Vec<i32> = countdown.collect();
assert_eq!(nums, vec![5, 4, 3, 2, 1]);
// CycleN
let cycle = CycleN::new(&[1, 2, 3], 2);
let repeated: Vec<i32> = cycle.collect();
assert_eq!(
repeated,
vec![1, 2, 3, 1, 2, 3]
);
// Collatz
let collatz = Collatz::new(6);
let sequence: Vec<u64> = collatz.collect();
assert_eq!(
sequence,
vec![6, 3, 10, 5, 16, 8, 4, 2, 1]
);
// Windows
let windows = Windows::new(&[1, 2, 3, 4, 5], 3);
let groups: Vec<Vec<i32>> = windows.collect();
assert_eq!(
groups,
vec![vec![1, 2, 3], vec![2, 3, 4], vec![3, 4, 5]]
);
// Unfold (powers of 2 until > 100)
let powers = Unfold::new(1u32, |&n| {
let next = n * 2;
if next <= 100 { Some(next) } else { None }
});
let result: Vec<u32> = powers.collect();
assert_eq!(
result,
vec![1, 2, 4, 8, 16, 32, 64]
);Hints
Click here for hints
- For
Fibonacci, store two consecutive numbers and update them each iteration - For
StepRange, validate step direction matches the relationship between start and end - For
CycleN, track both the current index in the slice and how many complete cycles remain - For
Collatz, use a flag or Option to track when you've yielded 1 and should stop - For
Windows, track the starting index and yield a slice/clone from that position - For
Unfold, the tricky part is yielding the initial value first, then subsequent values - Remember that
next()takes&mut self—you need mutable state to track progress - Use
Optionfields when you need to represent "already exhausted" states
Run to execute your code and see test results here.