ExamplesBy LevelBy TopicLearning Paths
284 Intermediate

284: FusedIterator for Stable Termination

Functional Programming

Tutorial Video

Text description (accessibility)

This video demonstrates the "284: FusedIterator for Stable Termination" functional Rust example. Difficulty level: Intermediate. Key concepts covered: Functional Programming. The `Iterator` specification in Rust says that after `next()` returns `None`, subsequent behavior is undefined — some iterators might return `Some` again if called further (a "restart"). Key difference from OCaml: 1. **Structural vs behavioral**: OCaml's `Seq.Nil` terminates the sequence structurally (you can't call it again); Rust requires an explicit contract via `FusedIterator`.

Tutorial

The Problem

The Iterator specification in Rust says that after next() returns None, subsequent behavior is undefined — some iterators might return Some again if called further (a "restart"). This makes it unsafe to call next() after termination without an explicit check. The FusedIterator marker trait solves this by promising that once next() returns None, all future calls also return None — enabling safe and optimized composition in adapters that call next() multiple times.

🎯 Learning Outcomes

  • • Understand FusedIterator as a marker trait guaranteeing "once None, always None"
  • • Implement FusedIterator correctly (only when the implementation actually fuses)
  • • Recognize that standard library adapters like filter(), map(), and zip() require FusedIterator for correctness guarantees
  • • Use fuse() on any iterator to wrap it in a guaranteed-fused adapter
  • Code Example

    use std::iter::FusedIterator;
    
    struct Countdown { n: i32 }
    
    impl Iterator for Countdown {
        type Item = i32;
        fn next(&mut self) -> Option<i32> {
            if self.n <= 0 { return None; }
            let val = self.n;
            self.n -= 1;
            Some(val)
        }
    }
    
    // Marker trait - enforces the guarantee
    impl FusedIterator for Countdown {}

    Key Differences

  • Structural vs behavioral: OCaml's Seq.Nil terminates the sequence structurally (you can't call it again); Rust requires an explicit contract via FusedIterator.
  • Adapter safety: Standard library adapters like peekable() and chain() explicitly document whether they require or provide FusedIterator.
  • **fuse() adapter**: Rust provides Iterator::fuse() to wrap any iterator in a fused version — FusedIterator is the opt-in promise, fuse() is the runtime enforcement.
  • Performance: Well-implemented FusedIterator types allow adapter code to skip the "check if already exhausted" bookkeeping.
  • OCaml Approach

    OCaml's Seq type is inherently fused — once a sequence node is Nil, there is no way to call next again (sequences are values, not objects). The concept of "calling next after None" does not apply to OCaml's immutable sequence type:

    (* OCaml Seq terminates structurally — the empty sequence is just Seq.empty *)
    let seq = Seq.empty  (* calling next "again" is impossible by construction *)
    

    Full Source

    #![allow(clippy::all)]
    //! # FusedIterator for Terminated Sequences
    //!
    //! `FusedIterator` guarantees: once `next()` returns `None`, all future calls also return `None`.
    
    use std::iter::FusedIterator;
    
    /// A properly fused countdown iterator
    pub struct Countdown {
        n: i32,
    }
    
    impl Countdown {
        pub fn new(n: i32) -> Self {
            Countdown { n }
        }
    }
    
    impl Iterator for Countdown {
        type Item = i32;
    
        fn next(&mut self) -> Option<i32> {
            if self.n <= 0 {
                return None;
            }
            let val = self.n;
            self.n -= 1;
            Some(val)
        }
    }
    
    // Declare that Countdown is fused — once None, always None
    impl FusedIterator for Countdown {}
    
    /// A badly-behaved iterator that returns Some after None (not fused)
    /// This is an anti-pattern - don't do this in real code
    pub struct FlickyIter {
        count: i32,
    }
    
    impl FlickyIter {
        pub fn new() -> Self {
            FlickyIter { count: 0 }
        }
    }
    
    impl Default for FlickyIter {
        fn default() -> Self {
            Self::new()
        }
    }
    
    impl Iterator for FlickyIter {
        type Item = i32;
    
        fn next(&mut self) -> Option<i32> {
            self.count += 1;
            if self.count == 3 {
                None // returns None once...
            } else if self.count < 6 {
                Some(self.count) // then Some again! (not fused)
            } else {
                None
            }
        }
    }
    
    /// Wraps any iterator to guarantee fused behavior
    /// (This is what `.fuse()` does internally)
    pub struct FusedWrapper<I> {
        inner: Option<I>,
    }
    
    impl<I> FusedWrapper<I> {
        pub fn new(iter: I) -> Self {
            FusedWrapper { inner: Some(iter) }
        }
    }
    
    impl<I: Iterator> Iterator for FusedWrapper<I> {
        type Item = I::Item;
    
        fn next(&mut self) -> Option<Self::Item> {
            match &mut self.inner {
                Some(iter) => match iter.next() {
                    Some(val) => Some(val),
                    None => {
                        self.inner = None; // Clear on first None
                        None
                    }
                },
                None => None, // Stay None forever
            }
        }
    }
    
    impl<I: Iterator> FusedIterator for FusedWrapper<I> {}
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_countdown_fused() {
            let mut cd = Countdown::new(3);
            assert_eq!(cd.next(), Some(3));
            assert_eq!(cd.next(), Some(2));
            assert_eq!(cd.next(), Some(1));
            assert_eq!(cd.next(), None);
            assert_eq!(cd.next(), None); // stays None
        }
    
        #[test]
        fn test_collect_countdown() {
            let result: Vec<i32> = Countdown::new(5).collect();
            assert_eq!(result, vec![5, 4, 3, 2, 1]);
        }
    
        #[test]
        fn test_flicky_is_not_fused() {
            let mut flicky = FlickyIter::new();
            assert_eq!(flicky.next(), Some(1));
            assert_eq!(flicky.next(), Some(2));
            assert_eq!(flicky.next(), None); // Returns None
            assert_eq!(flicky.next(), Some(4)); // But then Some again!
        }
    
        #[test]
        fn test_fuse_adapter() {
            let mut it = FlickyIter::new().fuse();
            assert_eq!(it.next(), Some(1));
            assert_eq!(it.next(), Some(2));
            assert_eq!(it.next(), None);
            assert_eq!(it.next(), None); // .fuse() guarantees this
        }
    
        #[test]
        fn test_custom_fused_wrapper() {
            let mut wrapped = FusedWrapper::new(FlickyIter::new());
            assert_eq!(wrapped.next(), Some(1));
            assert_eq!(wrapped.next(), Some(2));
            assert_eq!(wrapped.next(), None);
            assert_eq!(wrapped.next(), None); // Our wrapper also guarantees this
        }
    
        #[test]
        fn test_std_vec_is_fused() {
            let v = vec![1i32, 2, 3];
            let mut it = v.into_iter();
            assert_eq!(it.next(), Some(1));
            assert_eq!(it.next(), Some(2));
            assert_eq!(it.next(), Some(3));
            assert_eq!(it.next(), None);
            assert_eq!(it.next(), None); // std iterators are fused
        }
    
        #[test]
        fn test_countdown_sum() {
            let sum: i32 = Countdown::new(4).sum();
            assert_eq!(sum, 4 + 3 + 2 + 1);
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_countdown_fused() {
            let mut cd = Countdown::new(3);
            assert_eq!(cd.next(), Some(3));
            assert_eq!(cd.next(), Some(2));
            assert_eq!(cd.next(), Some(1));
            assert_eq!(cd.next(), None);
            assert_eq!(cd.next(), None); // stays None
        }
    
        #[test]
        fn test_collect_countdown() {
            let result: Vec<i32> = Countdown::new(5).collect();
            assert_eq!(result, vec![5, 4, 3, 2, 1]);
        }
    
        #[test]
        fn test_flicky_is_not_fused() {
            let mut flicky = FlickyIter::new();
            assert_eq!(flicky.next(), Some(1));
            assert_eq!(flicky.next(), Some(2));
            assert_eq!(flicky.next(), None); // Returns None
            assert_eq!(flicky.next(), Some(4)); // But then Some again!
        }
    
        #[test]
        fn test_fuse_adapter() {
            let mut it = FlickyIter::new().fuse();
            assert_eq!(it.next(), Some(1));
            assert_eq!(it.next(), Some(2));
            assert_eq!(it.next(), None);
            assert_eq!(it.next(), None); // .fuse() guarantees this
        }
    
        #[test]
        fn test_custom_fused_wrapper() {
            let mut wrapped = FusedWrapper::new(FlickyIter::new());
            assert_eq!(wrapped.next(), Some(1));
            assert_eq!(wrapped.next(), Some(2));
            assert_eq!(wrapped.next(), None);
            assert_eq!(wrapped.next(), None); // Our wrapper also guarantees this
        }
    
        #[test]
        fn test_std_vec_is_fused() {
            let v = vec![1i32, 2, 3];
            let mut it = v.into_iter();
            assert_eq!(it.next(), Some(1));
            assert_eq!(it.next(), Some(2));
            assert_eq!(it.next(), Some(3));
            assert_eq!(it.next(), None);
            assert_eq!(it.next(), None); // std iterators are fused
        }
    
        #[test]
        fn test_countdown_sum() {
            let sum: i32 = Countdown::new(4).sum();
            assert_eq!(sum, 4 + 3 + 2 + 1);
        }
    }

    Deep Comparison

    OCaml vs Rust: FusedIterator

    Pattern 1: Guaranteed Termination

    OCaml

    (* OCaml Seq.t is naturally fused by convention *)
    let countdown n =
      Seq.unfold (fun i -> if i <= 0 then None else Some (i, i-1)) n
    
    (* Once None, stays None - but convention, not enforced *)
    

    Rust

    use std::iter::FusedIterator;
    
    struct Countdown { n: i32 }
    
    impl Iterator for Countdown {
        type Item = i32;
        fn next(&mut self) -> Option<i32> {
            if self.n <= 0 { return None; }
            let val = self.n;
            self.n -= 1;
            Some(val)
        }
    }
    
    // Marker trait - enforces the guarantee
    impl FusedIterator for Countdown {}
    

    Pattern 2: Safety Wrapper

    Rust

    // If you don't trust an iterator, wrap it
    let safe = weird_iterator.fuse();
    // Now guaranteed: once None, always None
    

    Pattern 3: All Standard Iterators

    Rust

    let mut v = vec![1, 2, 3].into_iter();
    v.next(); // Some(1)
    v.next(); // Some(2) 
    v.next(); // Some(3)
    v.next(); // None
    v.next(); // None - all std iterators are fused
    

    Key Differences

    AspectOCamlRust
    GuaranteeConvention onlyFusedIterator marker trait
    EnforcementNot by type systemCompiler-checked
    Standard libraryAll well-behavedAll implement FusedIterator
    Safety wrapperN/A.fuse() adapter
    ImplementationProgrammer disciplineimpl FusedIterator for T {}

    Exercises

  • Implement a non-fused iterator (one that sometimes returns Some after None) and demonstrate the incorrect behavior, then fix it by fusing.
  • Show that iterator.fuse() wraps the iterator in a type that implements FusedIterator regardless of whether the original did.
  • Implement FusedIterator on a bounded random-number generator that stops after generating exactly N values.
  • Open Source Repos