ExamplesBy LevelBy TopicLearning Paths
286 Intermediate

286: Creating Iterators with from_fn()

Functional Programming

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

  • • Understand from_fn(f) as creating an iterator from a FnMut() -> Option<T> closure
  • • Use captured mutable variables in the closure to maintain iteration state
  • • Implement Fibonacci and other stateful sequences with less boilerplate than a struct
  • • Recognize from_fn as the bridge between stateful computation and the iterator ecosystem
  • Code Example

    let mut n = 0i32;
    let counter = std::iter::from_fn(move || {
        n += 1;
        if n <= 5 { Some(n) } else { None }
    });

    Key Differences

  • 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.
  • Functional vs imperative: OCaml's unfold is purely functional (state is passed explicitly); Rust's from_fn is imperative (state is mutated in place).
  • Termination: Both return None/None to signal exhaustion; checked_add in Rust enables overflow-safe termination.
  • Composability: Both integrate with their respective adapter ecosystems — 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]);
        }
    }
    ✓ Tests Rust test suite
    #[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

    AspectOCamlRust
    ConstructorSeq.unfold / Seq.of_dispenserstd::iter::from_fn
    Stateref cells in closuremove closure captures
    TerminationReturn NoneReturn None
    InfiniteSeq.foreverAlways return Some, use .take(n)
    Use caseGeneral unfold patternStateful generator without struct

    Exercises

  • Use from_fn to implement a random number generator iterator that produces pseudo-random numbers using an LCG (linear congruential generator) with captured seed state.
  • Implement the Collatz sequence using from_fn, terminating when the value reaches 1.
  • Build a retry_until_success iterator using from_fn that calls a fallible function and keeps retrying until it returns Ok, yielding the attempts.
  • Open Source Repos