909-iterator-skip-while — Iterator skip_while
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
.skip_while(pred) to skip a leading prefix satisfying a conditionCode Example
pub fn skip_less_than(slice: &[i32], threshold: i32) -> Vec<i32> {
slice
.iter()
.copied()
.skip_while(|&x| x < threshold)
.collect()
}Key Differences
.skip_while() and OCaml Seq.drop_while switch from skipping to yielding exactly once — later-matching elements are included in the output.skip_while stops skipping permanently; filter checks every element — they produce different results on non-monotone input..skip_while() on all iterators; OCaml requires Seq.drop_while (4.14+) or manual recursion.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]);
}
}#[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
| Concept | OCaml | Rust |
|---|---|---|
| Function signature | val skip_while : ('a -> bool) -> 'a list -> 'a list | fn skip_while_recursive<T, F>(slice: &[T], pred: F) -> &[T] |
| List/sequence type | 'a list | &[T] (slice) |
| Predicate type | 'a -> bool | F: Fn(&T) -> bool |
| Return type | 'a list (new list) | &[T] (sub-slice, zero-copy) |
Key Insights
&[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::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.[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.filter() which tests every element.'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
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).trim_zeros(data: &[i32]) -> &[i32] using skip_while for leading zeros and a manual slice end-trim for trailing zeros.skip_while to skip comment lines (starting with #) before processing data rows.