ExamplesBy LevelBy TopicLearning Paths
908 Intermediate

908-iterator-take-while — Iterator take_while

Functional Programming

Tutorial

The Problem

Sometimes you want all elements up to the first failure, not just those satisfying a predicate anywhere in the sequence. take_while differs from filter in one critical way: it stops permanently at the first non-matching element, rather than skipping and continuing. This makes it the only safe option for bounded consumption of infinite iterators — filter on an infinite iterator that eventually runs out of matches would loop forever. It also models "leading prefix" queries: collect all sorted-prefix elements, consume a stream until a sentinel, read until end-of-section.

🎯 Learning Outcomes

  • • Use .take_while(pred) to collect a leading prefix satisfying a condition
  • • Understand the "stops permanently" semantics vs filter's "skip and continue"
  • • Apply take_while to infinite iterators safely
  • • Implement the functional/recursive version for OCaml comparison
  • • Recognize real use cases: reading until a sentinel, consuming sorted prefixes
  • Code Example

    pub fn take_while_less_than(slice: &[i32], threshold: i32) -> Vec<i32> {
        slice.iter().copied().take_while(|&x| x < threshold).collect()
    }
    
    pub fn leading_positives(slice: &[i32]) -> Vec<i32> {
        slice.iter().copied().take_while(|&x| x > 0).collect()
    }
    
    // Works on infinite iterators — OCaml lists cannot be infinite
    pub fn triangular_indices_below(limit: u64) -> Vec<u64> {
        (1u64..).take_while(|&n| n * (n + 1) / 2 < limit).collect()
    }

    Key Differences

  • Infinite safety: Rust .take_while() terminates on infinite iterators; OCaml Seq.take_while also terminates; List.take_while requires finite input.
  • Standard library: Rust has .take_while() as a built-in method on all iterators; OCaml requires Seq.take_while (4.14+) or manual recursion for lists.
  • Filter vs take_while: Both languages have both operations; the distinction is "stops at first failure" vs "skips non-matching elements throughout."
  • Slice patterns: Rust's [x, rest @ ..] pattern in recursive implementations closely mirrors OCaml's x :: rest list destructuring.
  • OCaml Approach

    List.filteri is not the right analog — use the non-standard take_while. Standard OCaml lacks List.take_while; it requires manual recursion: let rec take_while p = function | [] -> [] | x :: rest -> if p x then x :: take_while p rest else []. For Seq: Seq.take_while: ('a -> bool) -> 'a Seq.t -> 'a Seq.t is available since 4.14 and has the same semantics as Rust's .take_while().

    Full Source

    #![allow(clippy::all)]
    //! 264. Conditional stopping with take_while()
    //!
    //! `take_while(pred)` yields elements until the predicate first returns false.
    //! Unlike `filter`, it stops permanently at the first non-matching element —
    //! making it the only viable option for infinite iterators.
    
    /// Idiomatic Rust: use the built-in iterator adapter.
    pub fn take_while_less_than(slice: &[i32], threshold: i32) -> Vec<i32> {
        slice
            .iter()
            .copied()
            .take_while(|&x| x < threshold)
            .collect()
    }
    
    /// Leading positives from a slice — stops at the first non-positive value.
    pub fn leading_positives(slice: &[i32]) -> Vec<i32> {
        slice.iter().copied().take_while(|&x| x > 0).collect()
    }
    
    /// Returns all n where the n-th triangular number (n*(n+1)/2) is below `limit`.
    /// Works on the infinite iterator `1u64..` — safe because take_while is lazy.
    pub fn triangular_indices_below(limit: u64) -> Vec<u64> {
        (1u64..).take_while(|&n| n * (n + 1) / 2 < limit).collect()
    }
    
    /// Functional/recursive — mirrors the OCaml implementation directly.
    pub fn take_while_rec<T, F>(slice: &[T], pred: F) -> Vec<T>
    where
        T: Copy,
        F: Fn(T) -> bool,
    {
        match slice {
            [] => vec![],
            [x, rest @ ..] => {
                if pred(*x) {
                    let mut result = vec![*x];
                    result.extend(take_while_rec(rest, pred));
                    result
                } else {
                    vec![]
                }
            }
        }
    }
    
    /// Leading alphabetic characters from a string.
    pub fn leading_alpha(s: &str) -> String {
        s.chars().take_while(|c| c.is_alphabetic()).collect()
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_take_while_less_than_stops_at_threshold() {
            assert_eq!(
                take_while_less_than(&[1, 2, 3, 4, 5, 6, 7, 8, 9], 5),
                vec![1, 2, 3, 4]
            );
        }
    
        #[test]
        fn test_take_while_less_than_empty_when_first_fails() {
            assert_eq!(take_while_less_than(&[5, 1, 2, 3], 5), vec![]);
        }
    
        #[test]
        fn test_take_while_less_than_all_match() {
            assert_eq!(take_while_less_than(&[1, 2, 3], 10), vec![1, 2, 3]);
        }
    
        #[test]
        fn test_take_while_less_than_empty_input() {
            assert_eq!(take_while_less_than(&[], 5), vec![]);
        }
    
        #[test]
        fn test_leading_positives_stops_at_negative() {
            assert_eq!(
                leading_positives(&[3, 1, 4, 1, -5, 9, -2, 6]),
                vec![3, 1, 4, 1]
            );
        }
    
        #[test]
        fn test_leading_positives_all_negative() {
            assert_eq!(leading_positives(&[-1, -2, -3]), vec![]);
        }
    
        #[test]
        fn test_triangular_indices_below_30() {
            assert_eq!(triangular_indices_below(30), vec![1, 2, 3, 4, 5, 6, 7]);
        }
    
        #[test]
        fn test_triangular_indices_below_1_is_empty() {
            assert_eq!(triangular_indices_below(1), vec![]);
        }
    
        #[test]
        fn test_recursive_mirrors_idiomatic() {
            let data = [1i32, 2, 3, 4, 5, 6];
            let idiomatic = take_while_less_than(&data, 4);
            let recursive = take_while_rec(&data, |x| x < 4);
            assert_eq!(idiomatic, recursive);
        }
    
        #[test]
        fn test_leading_alpha_stops_at_digit() {
            assert_eq!(leading_alpha("hello123world"), "hello".to_string());
        }
    
        #[test]
        fn test_leading_alpha_empty_string() {
            assert_eq!(leading_alpha(""), "".to_string());
        }
    
        #[test]
        fn test_does_not_resume_after_stop() {
            // Critical: take_while stops permanently — trailing 3 is NOT collected
            let nums = [1i32, 2, 3, 4, 5, 4, 3];
            assert_eq!(take_while_less_than(&nums, 4), vec![1, 2, 3]);
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_take_while_less_than_stops_at_threshold() {
            assert_eq!(
                take_while_less_than(&[1, 2, 3, 4, 5, 6, 7, 8, 9], 5),
                vec![1, 2, 3, 4]
            );
        }
    
        #[test]
        fn test_take_while_less_than_empty_when_first_fails() {
            assert_eq!(take_while_less_than(&[5, 1, 2, 3], 5), vec![]);
        }
    
        #[test]
        fn test_take_while_less_than_all_match() {
            assert_eq!(take_while_less_than(&[1, 2, 3], 10), vec![1, 2, 3]);
        }
    
        #[test]
        fn test_take_while_less_than_empty_input() {
            assert_eq!(take_while_less_than(&[], 5), vec![]);
        }
    
        #[test]
        fn test_leading_positives_stops_at_negative() {
            assert_eq!(
                leading_positives(&[3, 1, 4, 1, -5, 9, -2, 6]),
                vec![3, 1, 4, 1]
            );
        }
    
        #[test]
        fn test_leading_positives_all_negative() {
            assert_eq!(leading_positives(&[-1, -2, -3]), vec![]);
        }
    
        #[test]
        fn test_triangular_indices_below_30() {
            assert_eq!(triangular_indices_below(30), vec![1, 2, 3, 4, 5, 6, 7]);
        }
    
        #[test]
        fn test_triangular_indices_below_1_is_empty() {
            assert_eq!(triangular_indices_below(1), vec![]);
        }
    
        #[test]
        fn test_recursive_mirrors_idiomatic() {
            let data = [1i32, 2, 3, 4, 5, 6];
            let idiomatic = take_while_less_than(&data, 4);
            let recursive = take_while_rec(&data, |x| x < 4);
            assert_eq!(idiomatic, recursive);
        }
    
        #[test]
        fn test_leading_alpha_stops_at_digit() {
            assert_eq!(leading_alpha("hello123world"), "hello".to_string());
        }
    
        #[test]
        fn test_leading_alpha_empty_string() {
            assert_eq!(leading_alpha(""), "".to_string());
        }
    
        #[test]
        fn test_does_not_resume_after_stop() {
            // Critical: take_while stops permanently — trailing 3 is NOT collected
            let nums = [1i32, 2, 3, 4, 5, 4, 3];
            assert_eq!(take_while_less_than(&nums, 4), vec![1, 2, 3]);
        }
    }

    Deep Comparison

    OCaml vs Rust: Conditional Stopping with take_while()

    Side-by-Side Code

    OCaml

    let rec take_while pred = function
      | [] -> []
      | x :: xs -> if pred x then x :: take_while pred xs else []
    
    let () =
      let nums = [1; 2; 3; 4; 5; 6; 7; 8; 9] in
      let small = take_while (fun x -> x < 5) nums in
      Printf.printf "Less than 5: %s\n"
        (String.concat ", " (List.map string_of_int small));
    
      let data = [3; 1; 4; 1; -5; 9; -2; 6] in
      let positives = take_while (fun x -> x > 0) data in
      Printf.printf "Leading positives: %s\n"
        (String.concat ", " (List.map string_of_int positives))
    

    Rust (idiomatic)

    pub fn take_while_less_than(slice: &[i32], threshold: i32) -> Vec<i32> {
        slice.iter().copied().take_while(|&x| x < threshold).collect()
    }
    
    pub fn leading_positives(slice: &[i32]) -> Vec<i32> {
        slice.iter().copied().take_while(|&x| x > 0).collect()
    }
    
    // Works on infinite iterators — OCaml lists cannot be infinite
    pub fn triangular_indices_below(limit: u64) -> Vec<u64> {
        (1u64..).take_while(|&n| n * (n + 1) / 2 < limit).collect()
    }
    

    Rust (functional/recursive — mirrors OCaml)

    pub fn take_while_rec<T, F>(slice: &[T], pred: F) -> Vec<T>
    where
        T: Copy,
        F: Fn(T) -> bool,
    {
        match slice {
            [] => vec![],
            [x, rest @ ..] => {
                if pred(*x) {
                    let mut result = vec![*x];
                    result.extend(take_while_rec(rest, pred));
                    result
                } else {
                    vec![]
                }
            }
        }
    }
    

    Type Signatures

    ConceptOCamlRust
    Function signatureval take_while : ('a -> bool) -> 'a list -> 'a listfn take_while(pred: impl Fn(&T) -> bool) -> impl Iterator<Item=T>
    Sequence type'a list (finite, eager)impl Iterator<Item=T> (potentially infinite, lazy)
    Predicate'a -> boolFn(&T) -> bool or Fn(T) -> bool
    Output'a listVec<T> (after .collect())

    Key Insights

  • Laziness is the killer feature. Rust's take_while is lazy — it evaluates elements one at a time and stops immediately. OCaml's standard library operates on finite lists. Rust can apply take_while to (0u64..) (an infinite range) without issue; OCaml requires Seq.take_while for lazy sequences.
  • Permanent stop, not filter. Both OCaml and Rust take_while stop at the first predicate failure and never resume — this is the key distinction from filter. A trailing element that would match is silently excluded if it comes after a non-matching element.
  • Ownership and the closure. In Rust the predicate closure captures by reference and the double-dereference pattern |&&x| (or .copied() before take_while) handles the indirection introduced by .iter(). OCaml's GC avoids this entirely — values are freely passed without ownership concerns.
  • Slice patterns in the recursive version. Rust's [x, rest @ ..] slice pattern mirrors OCaml's x :: xs cons-cell pattern almost exactly, making the structural recursion legible to anyone familiar with OCaml pattern matching.
  • **take_while composes.** Because it is an iterator adapter, it can be chained with map, filter, enumerate, etc., without intermediate allocation — the full chain remains lazy until .collect() forces evaluation.
  • When to Use Each Style

    **Use idiomatic Rust (iter().take_while(...)) when:** working in a pipeline, dealing with infinite iterators, or when performance matters — the adapter is zero-cost and composes freely.

    Use recursive Rust when: teaching the OCaml parallel, working with algebraic data structures like linked lists, or when the recursive structure makes the termination condition more explicit for readers.

    Exercises

  • Use take_while to implement read_until_blank(lines: &[&str]) -> Vec<&str> that returns lines up to but not including the first empty line.
  • Write sorted_prefix(data: &[i32]) -> Vec<i32> that returns the longest initial sorted (non-decreasing) subsequence.
  • Combine take_while and skip_while to implement trim_both(data: &[i32], pred: impl Fn(&i32) -> bool) -> &[i32] that removes matching elements from both ends.
  • Open Source Repos