908-iterator-take-while — Iterator take_while
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
.take_while(pred) to collect a leading prefix satisfying a conditionfilter's "skip and continue"take_while to infinite iterators safelyCode 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
.take_while() terminates on infinite iterators; OCaml Seq.take_while also terminates; List.take_while requires finite input..take_while() as a built-in method on all iterators; OCaml requires Seq.take_while (4.14+) or manual recursion for lists.[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]);
}
}#[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
| Concept | OCaml | Rust |
|---|---|---|
| Function signature | val take_while : ('a -> bool) -> 'a list -> 'a list | fn 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 -> bool | Fn(&T) -> bool or Fn(T) -> bool |
| Output | 'a list | Vec<T> (after .collect()) |
Key Insights
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.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.|&&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.[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
take_while to implement read_until_blank(lines: &[&str]) -> Vec<&str> that returns lines up to but not including the first empty line.sorted_prefix(data: &[i32]) -> Vec<i32> that returns the longest initial sorted (non-decreasing) subsequence.take_while and skip_while to implement trim_both(data: &[i32], pred: impl Fn(&i32) -> bool) -> &[i32] that removes matching elements from both ends.