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
}
| Method | Produce | Ownership | Collection after |
|---|---|---|---|
iter() | &T | Borrow | Still valid |
iter_mut() | &mut T | Mutable borrowing | Still valid, modified |
into_iter() | T | Consume | Cannot 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 hasilAdapter — 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:#e3f2fdRust 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
mapandfilterdo not compute until the iterator is consumed. This allows long chaining without intermediate memory allocation.- Three ways of collection iteration —
iter()for borrow (&T),iter_mut()for mutable borrow (&mut T),into_iter()for consume (T). Incorrect choices are a common source of compile errors.mapfor transform,filterfor filtering,flat_mapfor flatten — these three are the most fundamental and most frequently used adapters.foldfor custom accumulation,collectfor collection to collection —foldis the most flexible consumer;collectcan aggregate toVec,HashMap,HashSet,String, orResult<Vec<T>, E>.anyandallshort-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:chainto combine two iterators,zipto create a pair,enumerateto 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.