Iterators #

If ownership is the feature that most differentiates Rust from other languages technically, iterators are the feature that most differentiates Rust in terms of code writing style. In Rust, iterators aren’t just a way to iterate over collections — they’re abstractions that compile into code as efficiently as a manual loop, without any overhead. Code written declaratively with map, filter, and fold produces binary that is identical to the loop for written imperatively. This is zero-cost abstraction in real practice. This article discusses how the Iterator trait works, the most frequently used adapters, how to consume iterators, how to create your own iterators, and when iterators are more appropriate to use than regular loops.

Trait Iterator — The Foundation of All Iteration #

All iterators in Rust implement the same trait: Iterator. This trait requires only one method to be implemented: next(), which returns Option<Self::Item>Some(item) as long as there are elements, None when they run out.

pub trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
    // ... hundreds of other methods with default implementations
}

The entire adapter ecosystem (map, filter, zip, etc.) is a method with a default implementation built on top of next(). By implementing next(), you get all these methods for free.

fn main() {
    let angka = vec![1, 2, 3, 4, 5];

    // into_iter() — consumes Vec, produces an iterator over values
    let mut iter = angka.into_iter();
    println!("{:?}", iter.next());  // Some(1)
    println!("{:?}", iter.next());  // Some(2)
    println!("{:?}", iter.next());  // Some(3)
    // ... to None

    // The for loop is syntactic sugar for into_iter() + next()
    let angka = vec![1, 2, 3];
    for n in angka {  // calls numbers.into_iter() behind the scenes
        println!("{}", n);
    }
}

Three Ways to Iterate a Collection #

There are three methods for creating iterators from collections, and the wrong choice is a very common source of bugs in Rust code.

fn main() {
    let v = vec![1, 2, 3, 4, 5];

    // iter() — borrows contents, returns &T
    // v is still valid after the loop completes
    for n in v.iter() {
        println!("{}", n);  // n is of type &i32
    }
    println!("v masih ada: {:?}", v);  // ✓

    // iter_mut() — mutable borrows content, returns &mut T
    let mut v2 = vec![1, 2, 3];
    for n in v2.iter_mut() {
        *n *= 2;  // modification in place
    }
    println!("{:?}", v2);  // [2, 4, 6]

    // into_iter() — consumes collection, returns T
    // v cannot be used again after this
    let v3 = vec![1, 2, 3];
    for n in v3.into_iter() {  // or: for n in v3
        println!("{}", n);  // n is type i32
    }
    // println!("{:?}", v3);  // ERROR: v3 has been moved
}
MethodProduceOwnershipCollection after
iter()&TBorrowStill valid
iter_mut()&mut TMutable borrowingStill valid, modified
into_iter()TConsumeCannot be used

Lazy Evaluation — Iterator Doesn’t Do Anything Until It’s Consumed #

This is the most important concept about iterators in Rust: iterators are lazy. Chaining map, filter, and other adapters doesn’t do any computing — it just creates a transformation description structure. A new computation occurs when the iterator is consumed by collect(), for, sum(), or another consumer.

fn main() {
    let v = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

    // This does NOT do any computing
    let transformasi = v.iter()
        .map(|x| {
            println!("map dipanggil untuk {}", x);  // has not yet been printed
            x * 2
        })
        .filter(|x| x % 3 == 0);

    println!("Sebelum dikonsumsi — belum ada output dari closure");

    // New computing happens here
    let hasil: Vec<i32> = transformasi.collect();
    println!("Hasil: {:?}", hasil);
}

Lazy evaluation has significant performance implications — the iterator chain does not allocate intermediate memory:

fn main() {
    let angka: Vec<i32> = (1..=1_000_000).collect();

    // ANTI-PATTERN: each step creates a new Vec — 3 large allocations
    let hasil = angka.iter()
        .map(|x| x * 2)
        .collect::<Vec<_>>()      // allocation 1
        .iter()
        .filter(|&&x| x > 100)
        .collect::<Vec<_>>()      // allocation 2
        .iter()
        .take(5)
        .collect::<Vec<_>>();     // allocation 3

    // CORRECT: one chain — one allocation at the end, zero intermediate
    let hasil: Vec<i32> = angka.iter()
        .map(|x| x * 2)
        .filter(|&&x| x > 100)
        .take(5)
        .copied()
        .collect();               // one allocation
}
sequenceDiagram
    participant Source as Vec sumber
    participant Map as map(|x| x*2)
    participant Filter as filter(|x| x>100)
    participant Take as take(5)
    participant Collect as collect()

    Collect->>Take: minta elemen berikutnya
    Take->>Filter: minta elemen berikutnya
    Filter->>Map: minta elemen berikutnya
    Map->>Source: next()
    Source-->>Map: Some(1)
    Map-->>Filter: Some(2)
    Filter-->>Take: None (2 tidak > 100, minta lagi)
    Note over Filter,Source: proses berlanjut sampai 5 elemen ditemukan
    Take-->>Collect: Some(nilai)
    Collect-->>Collect: simpan ke Vec hasil

Adapter — Iterator Transformation #

Adapter is a method that takes one iterator and produces a new iterator. They are lazy — they do not compute until they are consumed.

map — Transformation of Each Element #

map applies a closure to each element and generates an iterator over the transformed values.

fn main() {
    let angka = vec![1, 2, 3, 4, 5];

    // Simple transformation
    let dikali_dua: Vec<i32> = angka.iter().map(|&x| x * 2).collect();
    println!("{:?}", dikali_dua);  // [2, 4, 6, 8, 10]

    // Transformation to a different type
    let sebagai_string: Vec<String> = angka.iter().map(|x| x.to_string()).collect();
    println!("{:?}", sebagai_string);  // ["1", "2", "3", "4", "5"]

    // Struct transformation
    #[derive(Debug)]
    struct Produk { nama: String, harga: f64 }

    let produk = vec![
        Produk { nama: "Apel".to_string(), harga: 5000.0 },
        Produk { nama: "Pisang".to_string(), harga: 3000.0 },
        Produk { nama: "Jeruk".to_string(), harga: 7000.0 },
    ];

    let nama_produk: Vec<&str> = produk.iter().map(|p| p.nama.as_str()).collect();
    let setelah_diskon: Vec<f64> = produk.iter().map(|p| p.harga * 0.9).collect();

    println!("{:?}", nama_produk);    // ["Apple", "Banana", "Orange"]
    println!("{:?}", setelah_diskon); // [4500.0, 2700.0, 6300.0]
}

filter — Filters Elements #

filter passes only elements that satisfy the predicate.

fn main() {
    let angka = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

    // Simple filter
    let genap: Vec<&i32> = angka.iter().filter(|&&x| x % 2 == 0).collect();
    println!("{:?}", genap);  // [2, 4, 6, 8, 10]

    // Filter with complex conditions
    let rentang_tengah: Vec<&i32> = angka.iter()
        .filter(|&&x| x > 3 && x < 8)
        .collect();
    println!("{:?}", rentang_tengah);  // [4, 5, 6, 7]

    // filter_map — both filter and map, useful for operations that may fail
    let input = vec!["1", "dua", "3", "empat", "5"];
    let angka_valid: Vec<i32> = input.iter()
        .filter_map(|s| s.parse::<i32>().ok())
        .collect();
    println!("{:?}", angka_valid);  // [1, 3, 5]

    // Real scenario: filter structs based on fields
    #[derive(Debug)]
    struct Pengguna { nama: String, aktif: bool, skor: u32 }

    let pengguna = vec![
        Pengguna { nama: "Alice".to_string(), aktif: true, skor: 85 },
        Pengguna { nama: "Bob".to_string(), aktif: false, skor: 92 },
        Pengguna { nama: "Carol".to_string(), aktif: true, skor: 78 },
    ];

    let aktif_skor_tinggi: Vec<&Pengguna> = pengguna.iter()
        .filter(|p| p.aktif && p.skor >= 80)
        .collect();

    for p in &aktif_skor_tinggi {
        println!("{}: {}", p.nama, p.skor);  // Alice: 85
    }
}

flat_map — Map then Flatten #

flat_map applies a closure that returns an iterator for each element, then combines all the iterators into one.

fn main() {
    // Problem: each sentence has several words
    let kalimat = vec!["halo dunia", "rust sangat cepat", "belajar iterator"];

    // regular map produces Vec<Vec<&str>>
    let nested: Vec<Vec<&str>> = kalimat.iter()
        .map(|s| s.split_whitespace().collect())
        .collect();
    println!("{:?}", nested);  // [["hello", "world"], ["rust", "very", "fast"], ...]

    // flat_map produces Vec<&str> — already flat
    let semua_kata: Vec<&str> = kalimat.iter()
        .flat_map(|s| s.split_whitespace())
        .collect();
    println!("{:?}", semua_kata);  // ["hello", "world", "rust", "very", "fast", ...]

    // Alternative: map then flatten
    let semua_kata2: Vec<&str> = kalimat.iter()
        .map(|s| s.split_whitespace())
        .flatten()
        .collect();

    // Scenario: each user has multiple tags
    let pengguna_tag = vec![
        ("Alice", vec!["rust", "backend", "api"]),
        ("Bob", vec!["frontend", "typescript"]),
        ("Carol", vec!["rust", "embedded", "iot"]),
    ];

    let semua_tag: Vec<&str> = pengguna_tag.iter()
        .flat_map(|(_, tags)| tags.iter().copied())
        .collect();
    println!("{:?}", semua_tag);
    // ["rust", "backend", "api", "frontend", "typescript", "rust", "embedded", "iot"]
}

Other Frequently Used Adapters #

fn main() {
    let v = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

    // take — takes the first n elements
    let lima_pertama: Vec<_> = v.iter().take(5).collect();
    println!("{:?}", lima_pertama);  // [1, 2, 3, 4, 5]

    // skip — skip the first n elements
    let tanpa_tiga: Vec<_> = v.iter().skip(3).collect();
    println!("{:?}", tanpa_tiga);  // [4, 5, 6, 7, 8, 9, 10]

    // take_while — take while the condition is met
    let sebelum_besar: Vec<_> = v.iter().take_while(|&&x| x < 5).collect();
    println!("{:?}", sebelum_besar);  // [1, 2, 3, 4]

    // skip_while — skip as long as the condition is met
    let setelah_kecil: Vec<_> = v.iter().skip_while(|&&x| x < 5).collect();
    println!("{:?}", setelah_kecil);  // [5, 6, 7, 8, 9, 10]

    // enumerate — add index
    for (i, val) in v.iter().enumerate() {
        if i < 3 { println!("indeks {}: {}", i, val); }
    }
    // index 0: 1, index 1: 2, index 2: 3

    // zip — combine two iterators into a pair of tuples
    let huruf = vec!['a', 'b', 'c'];
    let angka = vec![1, 2, 3];
    let pasangan: Vec<_> = huruf.iter().zip(angka.iter()).collect();
    println!("{:?}", pasangan);  // [('a', 1), ('b', 2), ('c', 3)]

    // chain — connect two iterators
    let pertama = vec![1, 2, 3];
    let kedua = vec![4, 5, 6];
    let semua: Vec<_> = pertama.iter().chain(kedua.iter()).collect();
    println!("{:?}", semua);  // [1, 2, 3, 4, 5, 6]

    // peekable — peek at the next element without consuming it
    let mut iter = v.iter().peekable();
    if iter.peek() == Some(&&1) {
        println!("Dimulai dari 1");
    }
    println!("Elemen pertama: {:?}", iter.next());  // Some(1) — not yet finished

    // step_by — fetch every n elements
    let setiap_dua: Vec<_> = v.iter().step_by(2).collect();
    println!("{:?}", setiap_dua);  // [1, 3, 5, 7, 9]

    // rev — reverse order (only for DoubleEndedIterator)
    let terbalik: Vec<_> = v.iter().rev().collect();
    println!("{:?}", terbalik);  // [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

    // copied and cloned — copy the value from the reference
    let refs: Vec<&i32> = v.iter().collect();
    let nilai: Vec<i32> = refs.iter().copied().collect();   // for Copy types
    let kloned: Vec<i32> = refs.iter().cloned().collect();  // uses clone()
}

Consumer — Consuming Iterator #

Consumer is a method that consumes an iterator and returns a non-iterator value. Once called, the iterator cannot be used again.

collect — Collects to Collection #

collect() is the most frequently used consumer. It collects all the elements of the iterator into a collection.

use std::collections::{HashMap, HashSet, BTreeMap};

fn main() {
    let v = vec![1, 2, 3, 2, 1, 4, 5, 4];

    // To Vec
    let sebagai_vec: Vec<i32> = v.iter().copied().collect();

    // To HashSet — automatically removes duplicates
    let unik: HashSet<i32> = v.iter().copied().collect();
    println!("{:?}", unik);  // {1, 2, 3, 4, 5} (indeterminate order)

    // To HashMap of iterator of tuples
    let pasangan = vec![("a", 1), ("b", 2), ("c", 3)];
    let map: HashMap<&str, i32> = pasangan.into_iter().collect();
    println!("{:?}", map);

    // To String from iterator of char
    let huruf = vec!['h', 'a', 'l', 'o'];
    let kata: String = huruf.into_iter().collect();
    println!("{}", kata);  // "hello"

    // To String from iterator of &str
    let bagian = vec!["halo", " ", "dunia"];
    let kalimat: String = bagian.into_iter().collect();
    println!("{}", kalimat);  // "hello world"

    // To Result<Vec<T>, E> — fails if there is one error
    let input = vec!["1", "2", "3"];
    let angka: Result<Vec<i32>, _> = input.iter().map(|s| s.parse::<i32>()).collect();
    println!("{:?}", angka);  // Ok([1, 2, 3])
}

fold and reduce — Accumulate #

fold accumulates all elements into one value with the accumulator function and initial value. reduce is similar but uses the first element as the initial value.

fn main() {
    let angka = vec![1, 2, 3, 4, 5];

    // fold — accumulation with initial value
    let jumlah = angka.iter().fold(0, |akum, &x| akum + x);
    println!("Jumlah: {}", jumlah);  // 15

    let perkalian = angka.iter().fold(1, |akum, &x| akum * x);
    println!("Perkalian: {}", perkalian);  // 120

    // fold to build data structures
    let kata = vec!["halo", "dunia", "rust"];
    let kalimat = kata.iter().fold(String::new(), |mut akum, s| {
        if !akum.is_empty() { akum.push(' '); }
        akum.push_str(s);
        akum
    });
    println!("{}", kalimat);  // "hello rust world"

    // reduce — like fold but without initial value (returns Option)
    let maks = angka.iter().copied().reduce(|a, b| if a > b { a } else { b });
    println!("{:?}", maks);  // Some(5)

    let kosong: Vec<i32> = vec![];
    let maks_kosong = kosong.iter().copied().reduce(|a, b| a.max(b));
    println!("{:?}", maks_kosong);  // None

    // Real scenario: count word frequencies
    use std::collections::HashMap;

    let teks = "apel pisang apel jeruk pisang apel";
    let frekuensi: HashMap<&str, usize> = teks.split_whitespace()
        .fold(HashMap::new(), |mut map, kata| {
            *map.entry(kata).or_insert(0) += 1;
            map
        });
    println!("{:?}", frekuensi);
    // {"apples": 3, "bananas": 2, "oranges": 1}
}

Other Consumer #

fn main() {
    let v = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

    // sum and product — for numeric types
    let total: i32 = v.iter().sum();
    let hasil_kali: i32 = v.iter().copied().product();
    println!("Sum: {}, Product: {}", total, hasil_kali);

    // min, max, min_by, max_by
    println!("Min: {:?}", v.iter().min());  // Some(1)
    println!("Max: {:?}", v.iter().max());  // Some(10)

    // For floats (not implementing Ord), use min_by and max_by
    let float_v = vec![1.5f64, 3.2, 0.8, 4.1, 2.7];
    let maks_float = float_v.iter()
        .max_by(|a, b| a.partial_cmp(b).unwrap());
    println!("Max float: {:?}", maks_float);  // Some(4.1)

    // count — count elements
    let jumlah_genap = v.iter().filter(|&&x| x % 2 == 0).count();
    println!("Jumlah genap: {}", jumlah_genap);  // 5

    // any and all — condition check
    let ada_genap = v.iter().any(|&x| x % 2 == 0);     // true
    let semua_positif = v.iter().all(|&x| x > 0);       // true
    let semua_kecil = v.iter().all(|&x| x < 5);         // false
    println!("Ada genap: {}, Semua positif: {}", ada_genap, semua_positif);

    // find — first element that satisfies the condition
    let pertama_genap = v.iter().find(|&&x| x % 2 == 0);
    println!("{:?}", pertama_genap);  // Some(2)

    // position — index of the first element that satisfies the condition
    let pos = v.iter().position(|&x| x == 5);
    println!("{:?}", pos);  // Some(4)

    // for_each — like a for loop, for side effects
    v.iter().filter(|&&x| x % 2 == 0).for_each(|x| {
        println!("Genap: {}", x);
    });

    // last — last element
    println!("{:?}", v.iter().last());  // Some(10)

    // nth — nth element (0-indexed), consuming up to that element
    let mut iter = v.iter();
    println!("{:?}", iter.nth(4));  // Some(5)
}

Iterator of Range and Generator #

Rust provides several ways to create iterators without existing collections.

fn main() {
    // Range — Rust's built-in iterator
    let r: Vec<i32> = (1..=5).collect();       // [1, 2, 3, 4, 5] inclusive
    let r2: Vec<i32> = (1..5).collect();        // [1, 2, 3, 4] exclusive

    // Combination range with adapter
    let genap_kuadrat: Vec<i32> = (1..=10)
        .filter(|x| x % 2 == 0)
        .map(|x| x * x)
        .collect();
    println!("{:?}", genap_kuadrat);  // [4, 16, 36, 64, 100]

    // iter::repeat — repeats one value infinitely
    let lima_nol: Vec<i32> = std::iter::repeat(0).take(5).collect();
    println!("{:?}", lima_nol);  // [0, 0, 0, 0, 0]

    // iter::repeat_with — repeats the result of the closure
    let mut counter = 0;
    let terurut: Vec<i32> = std::iter::repeat_with(|| {
        counter += 1;
        counter
    }).take(5).collect();
    println!("{:?}", terurut);  // [1, 2, 3, 4, 5]

    // iter::once — one-element iterator
    let satu: Vec<i32> = std::iter::once(42).collect();
    println!("{:?}", satu);  // [42]

    // iter::empty — empty iterator
    let kosong: Vec<i32> = std::iter::empty().collect();

    // iter::successors — returns a sequence of previous values
    let fibonacci: Vec<u64> = std::iter::successors(Some((0u64, 1u64)), |(a, b)| {
        Some((*b, a + b))
    })
    .take(10)
    .map(|(a, _)| a)
    .collect();
    println!("{:?}", fibonacci);  // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

    // iter::from_fn — iterator of the generator function
    let mut nilai = 1;
    let pangkat_dua: Vec<u32> = std::iter::from_fn(|| {
        if nilai <= 512 {
            let hasil = Some(nilai);
            nilai *= 2;
            hasil
        } else {
            None
        }
    }).collect();
    println!("{:?}", pangkat_dua);  // [1, 2, 4, 8, 16, 32, 64, 128, 256, 512]
}

Custom Iterator — Own Implementation #

Creating your own iterator only requires implementing the Iterator trait with the next() method.

// Iterator for arithmetic series
struct DeretAritmatika {
    nilai_saat_ini: i32,
    beda: i32,
    batas: i32,
}

impl DeretAritmatika {
    fn new(mulai: i32, beda: i32, batas: i32) -> Self {
        DeretAritmatika {
            nilai_saat_ini: mulai,
            beda,
            batas,
        }
    }
}

impl Iterator for DeretAritmatika {
    type Item = i32;

    fn next(&mut self) -> Option<Self::Item> {
        if self.nilai_saat_ini > self.batas {
            return None;
        }
        let nilai = self.nilai_saat_ini;
        self.nilai_saat_ini += self.beda;
        Some(nilai)
    }
}

// Iterator for pairs of ordered elements (sliding window size 2)
struct Pasangan<I: Iterator> {
    iter: I,
    sebelumnya: Option<I::Item>,
}

impl<I: Iterator> Pasangan<I>
where
    I::Item: Clone,
{
    fn new(mut iter: I) -> Self {
        let sebelumnya = iter.next();
        Pasangan { iter, sebelumnya }
    }
}

impl<I: Iterator> Iterator for Pasangan<I>
where
    I::Item: Clone,
{
    type Item = (I::Item, I::Item);

    fn next(&mut self) -> Option<Self::Item> {
        let sekarang = self.iter.next()?;
        let prev = self.sebelumnya.take()?;
        self.sebelumnya = Some(sekarang.clone());
        Some((prev, sekarang))
    }
}

fn main() {
    // Use Arithmetic Series
    let deret = DeretAritmatika::new(1, 3, 20);
    let hasil: Vec<i32> = deret.collect();
    println!("{:?}", hasil);  // [1, 4, 7, 10, 13, 16, 19]

    // All adapters are available for free
    let jumlah: i32 = DeretAritmatika::new(1, 2, 10).sum();
    println!("Jumlah: {}", jumlah);  // 25 (1+3+5+7+9)

    // Use Pair
    let angka = vec![1, 2, 3, 4, 5];
    let pasangan = Pasangan::new(angka.into_iter());
    let hasil: Vec<_> = pasangan.collect();
    println!("{:?}", hasil);  // [(1,2), (2,3), (3,4), (4,5)]

    // Calculate the difference between consecutive elements
    let v = vec![1, 4, 9, 16, 25];
    let selisih: Vec<i32> = Pasangan::new(v.into_iter())
        .map(|(a, b)| b - a)
        .collect();
    println!("{:?}", selisih);  // [3, 5, 7, 9]
}

Comparison: Iterator vs Manual Loop #

Iterators and for loops are often considered stylistic choices, but there are real differences in readability and expression of intent.

fn main() {
    let data = vec![
        ("Alice", 85),
        ("Bob", 42),
        ("Carol", 91),
        ("Dave", 67),
        ("Eve", 88),
    ];

    // IMPERATIVE APPROACH: manual loop
    let mut hasil_imperatif = Vec::new();
    for (nama, skor) in &data {
        if *skor >= 70 {
            hasil_imperatif.push(format!("{}: {}", nama, skor));
        }
    }
    hasil_imperatif.sort();

    // FUNCTIONAL APPROACH: iterator chain
    let mut hasil_fungsional: Vec<String> = data.iter()
        .filter(|(_, skor)| *skor >= 70)
        .map(|(nama, skor)| format!("{}: {}", nama, skor))
        .collect();
    hasil_fungsional.sort();

    // Both produce the same output
    println!("{:?}", hasil_fungsional);
    // ["Alice: 85", "Carol: 91", "Dave: 67" — not entered, "Eve: 88"]

    // Cases where loops are more appropriate: complex early returns
    fn cari_dengan_logika_kompleks(v: &[i32]) -> Option<i32> {
        for &x in v {
            if x > 10 {
                // complex logic that is difficult to represent as a chain
                let intermediate = x * 2 - 5;
                if intermediate % 7 == 0 {
                    return Some(intermediate);
                }
            }
        }
        None
    }

    // Cases where iterators are more appropriate: explicit data transformations
    fn statistik(v: &[f64]) -> (f64, f64, f64) {
        let n = v.len() as f64;
        let rata_rata = v.iter().sum::<f64>() / n;
        let varians = v.iter()
            .map(|&x| (x - rata_rata).powi(2))
            .sum::<f64>() / n;
        let standar_deviasi = varians.sqrt();
        (rata_rata, varians, standar_deviasi)
    }
}
flowchart TD
    A{Pilih pendekatan iterasi} --> B{Transformasi data?}
    B -- Yes --> C[Iterator chain]
    B -- No --> D{Early return kompleks?}
    D -- Yes --> E[Loop manual]
    D -- No --> F{Side effects utama?}
    F -- Yes --> G[for_each or loop]
    F -- No --> C

    C --> C1[map, filter, fold, collect]
    E --> E1[for loop with return/break]
    G --> G1[for_each for side effects]

    style C fill:#e8f5e9
    style E fill:#fff3e0
    style G fill:#e3f2fd
Rust compiler aggressively optimizes iterator chains. Benchmarks show iterator chain performance is equivalent to or even better than manual loops for many cases, because LLVM can perform optimizations such as auto-vectorization more easily on predictable iterator patterns.

Frequently Used Idiomatic Patterns #

Some iterator patterns you’ll encounter over and over again in real Rust code.

use std::collections::HashMap;

fn main() {
    // 1. Dedup — remove sequential duplicates (after sort)
    let mut v = vec![3, 1, 2, 1, 3, 2, 1];
    v.sort();
    v.dedup();
    println!("{:?}", v);  // [1, 2, 3]

    // 2. Partition — separate into two groups
    let angka: Vec<i32> = (1..=10).collect();
    let (genap, ganjil): (Vec<i32>, Vec<i32>) = angka.iter()
        .partition(|&&x| x % 2 == 0);
    println!("Genap: {:?}", genap);  // [2, 4, 6, 8, 10]
    println!("Ganjil: {:?}", ganjil); // [1, 3, 5, 7, 9]

    // 3. Group by — group by key (with fold to HashMap)
    let kata = vec!["apel", "pisang", "anggur", "alpukat", "pir"];
    let per_huruf: HashMap<char, Vec<&str>> = kata.iter()
        .fold(HashMap::new(), |mut map, &kata| {
            map.entry(kata.chars().next().unwrap())
                .or_insert_with(Vec::new)
                .push(kata);
            map
        });
    println!("{:?}", per_huruf);
    // {'a': ["apple", "grape", "avocado"], 'p': ["banana", "pear"]}

    // 4. Flatten nested collections
    let nested = vec![vec![1, 2, 3], vec![4, 5], vec![6, 7, 8, 9]];
    let rata: Vec<i32> = nested.into_iter().flatten().collect();
    println!("{:?}", rata);  // [1, 2, 3, 4, 5, 6, 7, 8, 9]

    // 5. Zip and unzip
    let nama = vec!["Alice", "Bob", "Carol"];
    let skor = vec![85, 92, 78];
    let zipped: Vec<_> = nama.iter().zip(skor.iter()).collect();
    println!("{:?}", zipped);  // [("Alice", 85), ("Bob", 92), ("Carol", 78)]

    let (nama_lagi, skor_lagi): (Vec<_>, Vec<_>) = zipped.into_iter().unzip();

    // 6. Windows and chunks (for slices)
    let data = vec![1, 2, 3, 4, 5];
    let windows: Vec<&[i32]> = data.windows(3).collect();
    println!("{:?}", windows);  // [[1,2,3], [2,3,4], [3,4,5]]

    let chunks: Vec<&[i32]> = data.chunks(2).collect();
    println!("{:?}", chunks);   // [[1,2], [3,4], [5]]

    // 7. scan — like fold but produces every intermediate value
    let kumulatif: Vec<i32> = (1..=5)
        .scan(0, |akum, x| {
            *akum += x;
            Some(*akum)
        })
        .collect();
    println!("{:?}", kumulatif);  // [1, 3, 6, 10, 15]
}

Summary #

  • Iterators are lazy — adapters like map and filter do not compute until the iterator is consumed. This allows long chaining without intermediate memory allocation.
  • Three ways of collection iterationiter() for borrow (&T), iter_mut() for mutable borrow (&mut T), into_iter() for consume (T). Incorrect choices are a common source of compile errors.
  • map for transform, filter for filtering, flat_map for flatten — these three are the most fundamental and most frequently used adapters.
  • fold for custom accumulation, collect for collection to collectionfold is the most flexible consumer; collect can aggregate to Vec, HashMap, HashSet, String, or Result<Vec<T>, E>.
  • any and all short-circuit — stops at the first element that determines the result, does not iterate over the entire collection unnecessarily.
  • chain, zip, enumerate — frequently used combination adapters: chain to combine two iterators, zip to create a pair, enumerate to add an index.
  • Implement next() for a custom iterator — by implementing this one method, you get the entire adapter ecosystem for free.
  • Iterator vs manual loop — choose iterator for declarative and composable data transformations; choose loops for complex logic with multiple early returns or states that are difficult to represent as a chain.


← Previous: Options & Results   Next: Collections →

About | Author | Content Scope | Editorial Policy | Privacy Policy | Disclaimer | Contact