ExamplesBy LevelBy TopicLearning Paths
909 Intermediate

909-iterator-skip-while — Iterator skip_while

Functional Programming

Tutorial

The Problem

Many data processing tasks need to skip a leading section before the relevant data begins: skip log header lines until the first data row, skip sorted leading zeros before meaningful values, strip leading whitespace from a string. skip_while is the complement of take_while: it discards elements from the front until the predicate first fails, then yields all remaining elements — including later ones that match the predicate. This "once it switches, it never switches back" behavior is the key distinction from filter. It models a state machine with two states: skipping and yielding.

🎯 Learning Outcomes

  • • Use .skip_while(pred) to skip a leading prefix satisfying a condition
  • • Understand that elements matching the predicate after the skip point are still yielded
  • • Apply skip_while to strip leading zeros, whitespace, and sentinels
  • • Implement the recursive OCaml-style version for comparison
  • • Combine skip_while with take_while for two-sided trimming
  • Code Example

    pub fn skip_less_than(slice: &[i32], threshold: i32) -> Vec<i32> {
        slice
            .iter()
            .copied()
            .skip_while(|&x| x < threshold)
            .collect()
    }

    Key Differences

  • Once-switch semantics: Both Rust .skip_while() and OCaml Seq.drop_while switch from skipping to yielding exactly once — later-matching elements are included in the output.
  • filter distinction: skip_while stops skipping permanently; filter checks every element — they produce different results on non-monotone input.
  • Standard library: Rust has first-class .skip_while() on all iterators; OCaml requires Seq.drop_while (4.14+) or manual recursion.
  • String application: Rust's chars().skip_while(whitespace).collect() is idiomatic for lstrip; OCaml requires explicit index arithmetic.
  • OCaml Approach

    Standard OCaml lacks List.skip_while; manual recursion: let rec skip_while p = function | [] -> [] | x :: rest -> if p x then skip_while p rest else x :: rest. For Seq: Seq.drop_while: ('a -> bool) -> 'a Seq.t -> 'a Seq.t (since 4.14). String lstrip: String.sub s (let i = ref 0 in while !i < String.length s && s.[!i] = ' ' do incr i done; !i) (String.length s - !i). OCaml's lack of built-in skip_while for lists is a notable gap.

    Full Source

    #![allow(clippy::all)]
    //! 265. Conditional Skipping with skip_while()
    //!
    //! `skip_while(pred)` discards elements from the front of an iterator until the predicate
    //! first returns `false`, then yields *all* remaining elements — including later ones that
    //! match the predicate. This "once it switches, it never switches back" behavior distinguishes
    //! it from `filter()`.
    
    /// Skip elements while they are less than `threshold`, returning the rest.
    pub fn skip_less_than(slice: &[i32], threshold: i32) -> Vec<i32> {
        slice
            .iter()
            .copied()
            .skip_while(|&x| x < threshold)
            .collect()
    }
    
    /// Strip leading whitespace using `skip_while` on chars (returns owned String).
    pub fn lstrip_chars(s: &str) -> String {
        s.chars().skip_while(|c| c.is_whitespace()).collect()
    }
    
    /// Remove leading zeros from a slice.
    ///
    /// The trailing zero inside the sequence is preserved — skipping already stopped.
    pub fn strip_leading_zeros(slice: &[i32]) -> Vec<i32> {
        slice.iter().copied().skip_while(|&x| x == 0).collect()
    }
    
    /// Recursive implementation matching the OCaml `skip_while` idiom.
    ///
    /// OCaml pattern-matches on the list head; Rust does the same on a slice.
    pub fn skip_while_recursive<T, F>(slice: &[T], pred: F) -> &[T]
    where
        F: Fn(&T) -> bool,
    {
        match slice {
            [] => &[],
            [head, rest @ ..] if pred(head) => skip_while_recursive(rest, pred),
            _ => slice,
        }
    }
    
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_skip_less_than_typical() {
            let nums = [1, 2, 3, 4, 5, 4, 3, 2, 1];
            assert_eq!(skip_less_than(&nums, 4), vec![4, 5, 4, 3, 2, 1]);
        }
    
        #[test]
        fn test_skip_less_than_all_skipped() {
            let nums = [1, 2, 3];
            assert_eq!(skip_less_than(&nums, 10), Vec::<i32>::new());
        }
    
        #[test]
        fn test_skip_less_than_none_skipped() {
            let nums = [5, 6, 7];
            assert_eq!(skip_less_than(&nums, 1), vec![5, 6, 7]);
        }
    
        #[test]
        fn test_strip_leading_zeros_keeps_inner_zero() {
            let data = [0, 0, 0, 1, 2, 3, 0, 4];
            assert_eq!(strip_leading_zeros(&data), vec![1, 2, 3, 0, 4]);
        }
    
        #[test]
        fn test_strip_leading_zeros_all_zeros() {
            assert_eq!(strip_leading_zeros(&[0, 0, 0]), Vec::<i32>::new());
        }
    
        #[test]
        fn test_strip_leading_zeros_no_leading() {
            assert_eq!(strip_leading_zeros(&[1, 0, 2]), vec![1, 0, 2]);
        }
    
        #[test]
        fn test_lstrip_chars_basic() {
            assert_eq!(lstrip_chars("   hello world"), "hello world");
        }
    
        #[test]
        fn test_lstrip_chars_no_leading_space() {
            assert_eq!(lstrip_chars("hello"), "hello");
        }
    
        #[test]
        fn test_lstrip_chars_all_spaces() {
            assert_eq!(lstrip_chars("   "), "");
        }
    
        #[test]
        fn test_skip_while_recursive_matches_iter_adapter() {
            let nums = [1i32, 2, 3, 4, 5, 4, 3, 2, 1];
            let expected = skip_less_than(&nums, 4);
            let got = skip_while_recursive(&nums, |&x| x < 4);
            assert_eq!(got, expected.as_slice());
        }
    
        #[test]
        fn test_skip_while_recursive_empty() {
            let empty: &[i32] = &[];
            assert_eq!(skip_while_recursive(empty, |_| true), &[] as &[i32]);
        }
    }
    ✓ Tests Rust test suite
    #[cfg(test)]
    mod tests {
        use super::*;
    
        #[test]
        fn test_skip_less_than_typical() {
            let nums = [1, 2, 3, 4, 5, 4, 3, 2, 1];
            assert_eq!(skip_less_than(&nums, 4), vec![4, 5, 4, 3, 2, 1]);
        }
    
        #[test]
        fn test_skip_less_than_all_skipped() {
            let nums = [1, 2, 3];
            assert_eq!(skip_less_than(&nums, 10), Vec::<i32>::new());
        }
    
        #[test]
        fn test_skip_less_than_none_skipped() {
            let nums = [5, 6, 7];
            assert_eq!(skip_less_than(&nums, 1), vec![5, 6, 7]);
        }
    
        #[test]
        fn test_strip_leading_zeros_keeps_inner_zero() {
            let data = [0, 0, 0, 1, 2, 3, 0, 4];
            assert_eq!(strip_leading_zeros(&data), vec![1, 2, 3, 0, 4]);
        }
    
        #[test]
        fn test_strip_leading_zeros_all_zeros() {
            assert_eq!(strip_leading_zeros(&[0, 0, 0]), Vec::<i32>::new());
        }
    
        #[test]
        fn test_strip_leading_zeros_no_leading() {
            assert_eq!(strip_leading_zeros(&[1, 0, 2]), vec![1, 0, 2]);
        }
    
        #[test]
        fn test_lstrip_chars_basic() {
            assert_eq!(lstrip_chars("   hello world"), "hello world");
        }
    
        #[test]
        fn test_lstrip_chars_no_leading_space() {
            assert_eq!(lstrip_chars("hello"), "hello");
        }
    
        #[test]
        fn test_lstrip_chars_all_spaces() {
            assert_eq!(lstrip_chars("   "), "");
        }
    
        #[test]
        fn test_skip_while_recursive_matches_iter_adapter() {
            let nums = [1i32, 2, 3, 4, 5, 4, 3, 2, 1];
            let expected = skip_less_than(&nums, 4);
            let got = skip_while_recursive(&nums, |&x| x < 4);
            assert_eq!(got, expected.as_slice());
        }
    
        #[test]
        fn test_skip_while_recursive_empty() {
            let empty: &[i32] = &[];
            assert_eq!(skip_while_recursive(empty, |_| true), &[] as &[i32]);
        }
    }

    Deep Comparison

    OCaml vs Rust: Conditional Skipping with skip_while()

    Side-by-Side Code

    OCaml

    let rec skip_while pred = function
      | [] -> []
      | x :: xs as lst ->
        if pred x then skip_while pred xs else lst
    

    Rust (idiomatic — iterator adapter)

    pub fn skip_less_than(slice: &[i32], threshold: i32) -> Vec<i32> {
        slice
            .iter()
            .copied()
            .skip_while(|&x| x < threshold)
            .collect()
    }
    

    Rust (functional/recursive — mirrors OCaml)

    pub fn skip_while_recursive<T, F>(slice: &[T], pred: F) -> &[T]
    where
        F: Fn(&T) -> bool,
    {
        match slice {
            [] => &[],
            [head, rest @ ..] if pred(head) => skip_while_recursive(rest, pred),
            _ => slice,
        }
    }
    

    Type Signatures

    ConceptOCamlRust
    Function signatureval skip_while : ('a -> bool) -> 'a list -> 'a listfn skip_while_recursive<T, F>(slice: &[T], pred: F) -> &[T]
    List/sequence type'a list&[T] (slice)
    Predicate type'a -> boolF: Fn(&T) -> bool
    Return type'a list (new list)&[T] (sub-slice, zero-copy)

    Key Insights

  • Zero-copy slice returns: The recursive Rust version returns &[T] — a sub-slice pointing into the original allocation — whereas OCaml returns a shared reference to the existing list tail. Both avoid copying, but Rust makes the ownership explicit in the type.
  • Iterator adapter vs recursion: Idiomatic Rust delegates to Iterator::skip_while, which is lazy and composes freely with other adapters. OCaml's idiomatic form is direct recursion on a linked list; there is no lazy iterator in the stdlib equivalent.
  • Slice patterns mirror list patterns: [head, rest @ ..] in Rust is the direct equivalent of x :: xs in OCaml, making the recursive translation nearly mechanical. The @ .. rest-binding is Rust's spread pattern.
  • "Once stopped, never restarts" semantic: Both languages share this fundamental contract — the predicate is consulted only during the initial prefix scan. Elements after the first failure are yielded unconditionally, unlike filter() which tests every element.
  • Generics vs polymorphism: OCaml's 'a type parameter is inferred and implicit; Rust requires an explicit <T> and a trait bound F: Fn(&T) -> bool for the predicate. The result is the same expressiveness with explicit contracts.
  • When to Use Each Style

    **Use idiomatic Rust (skip_while adapter) when:** composing with other iterator adapters (.chain(), .map(), .collect()), processing large streams lazily, or stripping structured prefixes like CSV comment lines or log headers.

    Use recursive Rust when: demonstrating the OCaml translation clearly, working with recursive data structures where the iterator model doesn't map naturally, or when you need to return a sub-slice reference without allocating.

    Exercises

  • Implement skip_until<T: PartialEq + Clone>(data: &[T], sentinel: &T) -> Vec<T> that skips elements until the sentinel is found, then yields all remaining (excluding the sentinel).
  • Write trim_zeros(data: &[i32]) -> &[i32] using skip_while for leading zeros and a manual slice end-trim for trailing zeros.
  • Implement a CSV reader that uses skip_while to skip comment lines (starting with #) before processing data rows.
  • Open Source Repos