๐Ÿฆ€ Functional Rust
๐ŸŽฌ How Rust Iterators Work Lazy evaluation, chaining, collect(), and zero-cost abstractions.
๐Ÿ“ Text version (for readers / accessibility)

โ€ข Iterators are lazy โ€” .map(), .filter(), .take() build a chain but do no work until consumed

โ€ข .collect() triggers evaluation, transforming the chain into a Vec, HashMap, or other collection

โ€ข Zero-cost abstraction: iterator chains compile to the same machine code as hand-written loops

โ€ข .iter() borrows, .into_iter() consumes, .iter_mut() borrows mutably

โ€ข Chaining replaces nested loops with a readable, composable pipeline

265: Conditional Skipping with skip_while()

Difficulty: 1 Level: Beginner Skip elements from the front of an iterator until a condition fails, then yield everything that remains.

The Problem This Solves

You have a stream with a header or leading junk you want to discard. CSV files with comment lines starting with `#`. Log files where the first N lines are metadata. A number sequence with leading zeros. A string with leading whitespace. Without `skip_while`, you'd use a flag variable and a manual check inside a for loop โ€” or you'd `enumerate()` and check the index, which only works when you know the prefix length in advance. `skip_while` is the dual of `take_while`: instead of consuming up to a point, it discards up to a point. The critical semantic to internalize is that it stops skipping at the first failure and then yields everything after โ€” including later elements that match the predicate. This "once it switches, it doesn't switch back" behavior is what makes it useful for ordered prefixes and what distinguishes it from `filter()`.

The Intuition

`skip_while(pred)` discards elements as long as the predicate returns `true`. The first time it returns `false`, skipping stops and all remaining elements are yielded โ€” regardless of whether they match the predicate.
let nums = [0, 0, 0, 1, 2, 3, 0, 4];
let result: Vec<_> = nums.iter().skip_while(|&&x| x == 0).collect();
// โ†’ [1, 2, 3, 0, 4]   the trailing 0 is kept โ€” skipping already stopped

How It Works in Rust

// Strip leading whitespace (idiomatic ltrim)
let input = "   hello world";
let stripped: String = input.chars()
 .skip_while(|c| c.is_whitespace())
 .collect();
// โ†’ "hello world"

// Skip leading zeros โ€” later zeros are preserved
let with_zeros = [0i32, 0, 0, 1, 2, 3, 0, 4];
let no_leading: Vec<i32> = with_zeros.iter().copied()
 .skip_while(|&x| x == 0)
 .collect();
// โ†’ [1, 2, 3, 0, 4]

// skip_while + take_while to extract a range from a sorted sequence
let nums = [1i32, 2, 3, 4, 5, 4, 3, 2, 1];
let range: Vec<i32> = nums.iter().copied()
 .skip_while(|&x| x < 3)   // skip prefix below 3
 .take_while(|&x| x < 6)   // stop when >= 6
 .collect();
// โ†’ [3, 4, 5, 4, 3, 2, 1]   note: take_while stops at 6, but everything up to it passes
`skip_while` sees the whole remaining iterator once skipping stops. Unlike `filter`, it doesn't inspect every element โ€” it reads forward only until the predicate fails, then hands off the rest as-is.

What This Unlocks

Key Differences

ConceptOCamlRust
Skip prefix by predicate`Seq.drop_while` (lazy) / manual for lists`iter.skip_while(pred)`
Resumes after first failureYes (same semantics)Yes โ€” all remaining elements yielded
vs. `filter``filter` checks every element`skip_while` checks only the prefix
Pair with `take_while`Manual composition`.skip_while(p1).take_while(p2)` for ranges
//! 265. Conditional skipping with skip_while()
//!
//! `skip_while(pred)` discards elements until predicate first returns false, then yields all remaining.

fn main() {
    let nums = [1i32, 2, 3, 4, 5, 4, 3, 2, 1];

    let from_4: Vec<i32> = nums.iter().copied().skip_while(|&x| x < 4).collect();
    println!("Skip <4: {:?}", from_4);
    // Note: [4,5,4,3,2,1] โ€” trailing 3,2,1 are included

    let input = "   hello world";
    let stripped: String = input.chars().skip_while(|c| c.is_whitespace()).collect();
    println!("Stripped: '{}'", stripped);

    let with_zeros = [0i32, 0, 0, 1, 2, 3, 0, 4];
    let no_leading: Vec<i32> = with_zeros.iter().copied()
        .skip_while(|&x| x == 0).collect();
    println!("No leading zeros: {:?}", no_leading);
    // The 0 at position 6 is kept

    // Combine skip_while + take_while for range extraction
    let range: Vec<i32> = nums.iter().copied()
        .skip_while(|&x| x < 3)
        .take_while(|&x| x < 6)
        .collect();
    println!("Elements in [3,6): {:?}", range);
}

#[cfg(test)]
mod tests {
    #[test]
    fn test_skip_while_basic() {
        let result: Vec<i32> = [1, 2, 3, 4, 5].iter().copied()
            .skip_while(|&x| x < 3).collect();
        assert_eq!(result, vec![3, 4, 5]);
    }

    #[test]
    fn test_skip_while_includes_later_matches() {
        let result: Vec<i32> = [0i32, 0, 1, 0].iter().copied()
            .skip_while(|&x| x == 0).collect();
        assert_eq!(result, vec![1, 0]);
    }

    #[test]
    fn test_skip_while_all() {
        let result: Vec<i32> = [1, 2, 3].iter().copied()
            .skip_while(|&x| x < 10).collect();
        assert!(result.is_empty());
    }

    #[test]
    fn test_skip_while_none() {
        let result: Vec<i32> = [1, 2, 3].iter().copied()
            .skip_while(|&x| x > 10).collect();
        assert_eq!(result, vec![1, 2, 3]);
    }
}
(* 265. Conditional skipping with skip_while() - OCaml *)

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

let () =
  let nums = [1; 2; 3; 4; 5; 4; 3; 2; 1] in
  let from_4 = skip_while (fun x -> x < 4) nums in
  Printf.printf "Skip <4: %s\n"
    (String.concat ", " (List.map string_of_int from_4));

  let tokens = [' '; ' '; 'h'; 'e'; 'l'; 'l'; 'o'] in
  let stripped = skip_while (fun c -> c = ' ') tokens in
  Printf.printf "Stripped: '%s'\n"
    (String.concat "" (List.map (String.make 1) stripped));

  let with_zeros = [0; 0; 0; 1; 2; 3; 0; 4] in
  let no_leading = skip_while (fun x -> x = 0) with_zeros in
  Printf.printf "No leading zeros: %s\n"
    (String.concat ", " (List.map string_of_int no_leading))