ExamplesBy LevelBy TopicLearning Paths
260 Intermediate

260: Stateful Accumulation with scan()

Functional Programming

Tutorial

The Problem

Computing running totals, prefix sums, or any sequence where each value depends on all previous values is a common need in financial systems, signal processing, and data streaming. A plain fold() produces only the final accumulated result. The scan() adapter solves this by emitting each intermediate accumulated state as an element — turning a fold into a stream of partial results.

🎯 Learning Outcomes

  • • Understand scan() as fold() that yields each intermediate state rather than only the final value
  • • Use mutable accumulator state inside a scan() closure
  • • Return None from a scan() closure to terminate the sequence early
  • • Build running totals, prefix products, and other prefix-sum structures
  • Code Example

    #![allow(clippy::all)]
    //! 260. Stateful accumulation with scan()
    //!
    //! `scan()` is like `fold` but emits each intermediate state as an iterator element.
    
    #[cfg(test)]
    mod tests {
        #[test]
        fn test_scan_running_sum() {
            let result: Vec<i32> = [1, 2, 3, 4, 5]
                .iter()
                .scan(0i32, |s, &x| {
                    *s += x;
                    Some(*s)
                })
                .collect();
            assert_eq!(result, vec![1, 3, 6, 10, 15]);
        }
    
        #[test]
        fn test_scan_early_stop() {
            let result: Vec<i32> = [1, 2, 3, 4, 5]
                .iter()
                .scan(0i32, |s, &x| {
                    *s += x;
                    if *s > 6 {
                        None
                    } else {
                        Some(*s)
                    }
                })
                .collect();
            assert_eq!(result, vec![1, 3, 6]);
        }
    
        #[test]
        fn test_scan_product() {
            let result: Vec<i32> = [1, 2, 3, 4]
                .iter()
                .scan(1i32, |s, &x| {
                    *s *= x;
                    Some(*s)
                })
                .collect();
            assert_eq!(result, vec![1, 2, 6, 24]);
        }
    }

    Key Differences

  • Output: scan() emits every intermediate state; OCaml's equivalent must explicitly accumulate intermediates into a list.
  • Early termination: Rust's scan() can return None to stop the sequence; a fold-based OCaml approach requires extra logic.
  • Mutable reference: Rust's closure receives &mut state, making mutation explicit; OCaml uses functional update via return value.
  • Real-world use: Used in NumPy as np.cumsum(), in databases as window functions, and in streaming analytics as running aggregates.
  • OCaml Approach

    OCaml's List.scan_left (introduced in recent versions) directly mirrors this pattern, or one can build it with List.fold_left accumulating into a reversed list of intermediates:

    (* Manual scan: fold collecting intermediates *)
    let scan_left f init xs =
      List.rev (snd (List.fold_left (fun (acc, lst) x ->
        let acc' = f acc x in (acc', acc' :: lst)
      ) (init, []) xs))
    

    OCaml's Seq module makes this natural for lazy streams.

    Full Source

    #![allow(clippy::all)]
    //! 260. Stateful accumulation with scan()
    //!
    //! `scan()` is like `fold` but emits each intermediate state as an iterator element.
    
    #[cfg(test)]
    mod tests {
        #[test]
        fn test_scan_running_sum() {
            let result: Vec<i32> = [1, 2, 3, 4, 5]
                .iter()
                .scan(0i32, |s, &x| {
                    *s += x;
                    Some(*s)
                })
                .collect();
            assert_eq!(result, vec![1, 3, 6, 10, 15]);
        }
    
        #[test]
        fn test_scan_early_stop() {
            let result: Vec<i32> = [1, 2, 3, 4, 5]
                .iter()
                .scan(0i32, |s, &x| {
                    *s += x;
                    if *s > 6 {
                        None
                    } else {
                        Some(*s)
                    }
                })
                .collect();
            assert_eq!(result, vec![1, 3, 6]);
        }
    
        #[test]
        fn test_scan_product() {
            let result: Vec<i32> = [1, 2, 3, 4]
                .iter()
                .scan(1i32, |s, &x| {
                    *s *= x;
                    Some(*s)
                })
                .collect();
            assert_eq!(result, vec![1, 2, 6, 24]);
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        #[test]
        fn test_scan_running_sum() {
            let result: Vec<i32> = [1, 2, 3, 4, 5]
                .iter()
                .scan(0i32, |s, &x| {
                    *s += x;
                    Some(*s)
                })
                .collect();
            assert_eq!(result, vec![1, 3, 6, 10, 15]);
        }
    
        #[test]
        fn test_scan_early_stop() {
            let result: Vec<i32> = [1, 2, 3, 4, 5]
                .iter()
                .scan(0i32, |s, &x| {
                    *s += x;
                    if *s > 6 {
                        None
                    } else {
                        Some(*s)
                    }
                })
                .collect();
            assert_eq!(result, vec![1, 3, 6]);
        }
    
        #[test]
        fn test_scan_product() {
            let result: Vec<i32> = [1, 2, 3, 4]
                .iter()
                .scan(1i32, |s, &x| {
                    *s *= x;
                    Some(*s)
                })
                .collect();
            assert_eq!(result, vec![1, 2, 6, 24]);
        }
    }

    Exercises

  • Compute prefix products of a slice using scan() — the product of all elements up to and including each position.
  • Use scan() to implement a running maximum: each output is the maximum seen so far.
  • Build a balance tracker: given a sequence of deposit/withdrawal amounts, use scan() to emit the running account balance and terminate when it goes negative.
  • Open Source Repos