Iterators

Iterators #

Jika ownership adalah fitur yang paling membedakan Rust dari bahasa lain secara teknikal, iterator adalah fitur yang paling membedakan Rust dari sisi gaya penulisan kode. Di Rust, iterator bukan sekadar cara untuk mengulang koleksi — mereka adalah abstraksi yang dikompilasi menjadi kode seefisien loop manual, tanpa overhead apapun. Kode yang ditulis secara deklaratif dengan map, filter, dan fold menghasilkan binary yang identik dengan loop for yang ditulis secara imperatif. Ini adalah zero-cost abstraction dalam praktik nyata. Artikel ini membahas bagaimana trait Iterator bekerja, adapter-adapter yang paling sering digunakan, cara mengkonsumsi iterator, cara membuat iterator sendiri, dan kapan iterator lebih tepat digunakan dibanding loop biasa.

Trait Iterator — Fondasi Semua Iterasi #

Semua iterator di Rust mengimplementasikan satu trait yang sama: Iterator. Trait ini hanya membutuhkan satu method untuk diimplementasikan: next(), yang mengembalikan Option<Self::Item>Some(item) selama masih ada elemen, None saat habis.

pub trait Iterator {
    type Item;
    fn next(&mut self) -> Option<Self::Item>;
    // ... ratusan method lain dengan implementasi default
}

Seluruh ekosistem adapter (map, filter, zip, dll.) adalah method dengan implementasi default yang dibangun di atas next(). Dengan mengimplementasikan next(), kamu mendapat semua method tersebut secara gratis.

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

    // into_iter() — mengonsumsi Vec, menghasilkan iterator atas nilai
    let mut iter = angka.into_iter();
    println!("{:?}", iter.next());  // Some(1)
    println!("{:?}", iter.next());  // Some(2)
    println!("{:?}", iter.next());  // Some(3)
    // ... sampai None

    // Loop for adalah syntactic sugar untuk into_iter() + next()
    let angka = vec![1, 2, 3];
    for n in angka {  // memanggil angka.into_iter() di balik layar
        println!("{}", n);
    }
}

Tiga Cara Mengiterasi Koleksi #

Ada tiga method untuk membuat iterator dari koleksi, dan pilihan yang salah adalah sumber bug yang sangat umum di kode Rust.

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

    // iter() — meminjam isi, menghasilkan &T
    // v masih valid setelah loop selesai
    for n in v.iter() {
        println!("{}", n);  // n bertipe &i32
    }
    println!("v masih ada: {:?}", v);  // ✓

    // iter_mut() — meminjam isi secara mutable, menghasilkan &mut T
    let mut v2 = vec![1, 2, 3];
    for n in v2.iter_mut() {
        *n *= 2;  // modifikasi di tempat
    }
    println!("{:?}", v2);  // [2, 4, 6]

    // into_iter() — mengonsumsi koleksi, menghasilkan T
    // v tidak bisa dipakai lagi setelah ini
    let v3 = vec![1, 2, 3];
    for n in v3.into_iter() {  // atau: for n in v3
        println!("{}", n);  // n bertipe i32
    }
    // println!("{:?}", v3);  // ERROR: v3 sudah di-move
}
MethodMenghasilkanOwnershipKoleksi setelahnya
iter()&TPinjamMasih valid
iter_mut()&mut TPinjam mutableMasih valid, sudah dimodifikasi
into_iter()TConsumeTidak bisa dipakai

Lazy Evaluation — Iterator Tidak Melakukan Apapun Sampai Dikonsumsi #

Ini adalah konsep paling penting tentang iterator di Rust: iterator bersifat lazy. Merantai map, filter, dan adapter lainnya tidak melakukan komputasi apapun — ia hanya membuat struktur deskripsi transformasi. Komputasi baru terjadi saat iterator dikonsumsi oleh collect(), for, sum(), atau consumer lainnya.

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

    // Ini TIDAK melakukan komputasi apapun
    let transformasi = v.iter()
        .map(|x| {
            println!("map dipanggil untuk {}", x);  // belum tercetak
            x * 2
        })
        .filter(|x| x % 3 == 0);

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

    // Komputasi baru terjadi di sini
    let hasil: Vec<i32> = transformasi.collect();
    println!("Hasil: {:?}", hasil);
}

Lazy evaluation memiliki implikasi performa yang signifikan — iterator chain tidak mengalokasikan memori intermediate:

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

    // ANTI-PATTERN: setiap step membuat Vec baru — 3 alokasi besar
    let hasil = angka.iter()
        .map(|x| x * 2)
        .collect::<Vec<_>>()      // alokasi 1
        .iter()
        .filter(|&&x| x > 100)
        .collect::<Vec<_>>()      // alokasi 2
        .iter()
        .take(5)
        .collect::<Vec<_>>();     // alokasi 3

    // BENAR: satu chain — satu alokasi di akhir, zero intermediate
    let hasil: Vec<i32> = angka.iter()
        .map(|x| x * 2)
        .filter(|&&x| x > 100)
        .take(5)
        .copied()
        .collect();               // satu alokasi
}
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 — Transformasi Iterator #

Adapter adalah method yang mengambil satu iterator dan menghasilkan iterator baru. Mereka lazy — tidak melakukan komputasi sampai dikonsumsi.

map — Transformasi Setiap Elemen #

map mengaplikasikan closure ke setiap elemen dan menghasilkan iterator atas nilai yang sudah ditransformasi.

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

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

    // Transformasi ke tipe berbeda
    let sebagai_string: Vec<String> = angka.iter().map(|x| x.to_string()).collect();
    println!("{:?}", sebagai_string);  // ["1", "2", "3", "4", "5"]

    // Transformasi struct
    #[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);    // ["Apel", "Pisang", "Jeruk"]
    println!("{:?}", setelah_diskon); // [4500.0, 2700.0, 6300.0]
}

filter — Menyaring Elemen #

filter meneruskan hanya elemen yang memenuhi predikat.

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

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

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

    // filter_map — filter dan map sekaligus, berguna untuk operasi yang mungkin gagal
    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]

    // Skenario nyata: filter struct berdasarkan field
    #[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 lalu Flatten #

flat_map mengaplikasikan closure yang mengembalikan iterator untuk setiap elemen, lalu menggabungkan semua iterator tersebut menjadi satu.

fn main() {
    // Masalah: setiap kalimat punya beberapa kata
    let kalimat = vec!["halo dunia", "rust sangat cepat", "belajar iterator"];

    // map biasa menghasilkan Vec<Vec<&str>>
    let nested: Vec<Vec<&str>> = kalimat.iter()
        .map(|s| s.split_whitespace().collect())
        .collect();
    println!("{:?}", nested);  // [["halo", "dunia"], ["rust", "sangat", "cepat"], ...]

    // flat_map menghasilkan Vec<&str> — sudah rata
    let semua_kata: Vec<&str> = kalimat.iter()
        .flat_map(|s| s.split_whitespace())
        .collect();
    println!("{:?}", semua_kata);  // ["halo", "dunia", "rust", "sangat", "cepat", ...]

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

    // Skenario: setiap pengguna punya beberapa tag
    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"]
}

Adapter Lainnya yang Sering Digunakan #

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

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

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

    // take_while — ambil selama kondisi terpenuhi
    let sebelum_besar: Vec<_> = v.iter().take_while(|&&x| x < 5).collect();
    println!("{:?}", sebelum_besar);  // [1, 2, 3, 4]

    // skip_while — lewati selama kondisi terpenuhi
    let setelah_kecil: Vec<_> = v.iter().skip_while(|&&x| x < 5).collect();
    println!("{:?}", setelah_kecil);  // [5, 6, 7, 8, 9, 10]

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

    // zip — gabungkan dua iterator menjadi pasangan tuple
    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 — sambungkan dua iterator
    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 — intip elemen berikutnya tanpa mengonsumsinya
    let mut iter = v.iter().peekable();
    if iter.peek() == Some(&&1) {
        println!("Dimulai dari 1");
    }
    println!("Elemen pertama: {:?}", iter.next());  // Some(1) — belum habis

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

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

    // copied dan cloned — salin nilai dari referensi
    let refs: Vec<&i32> = v.iter().collect();
    let nilai: Vec<i32> = refs.iter().copied().collect();   // untuk Copy types
    let kloned: Vec<i32> = refs.iter().cloned().collect();  // menggunakan clone()
}

Consumer — Mengonsumsi Iterator #

Consumer adalah method yang menghabiskan iterator dan menghasilkan nilai non-iterator. Setelah dipanggil, iterator tidak bisa digunakan lagi.

collect — Mengumpulkan ke Koleksi #

collect() adalah consumer yang paling sering digunakan. Ia mengumpulkan semua elemen iterator ke dalam koleksi.

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

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

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

    // Ke HashSet — otomatis menghapus duplikat
    let unik: HashSet<i32> = v.iter().copied().collect();
    println!("{:?}", unik);  // {1, 2, 3, 4, 5} (urutan tidak tentu)

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

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

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

    // Ke Result<Vec<T>, E> — gagal jika ada satu 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 dan reduce — Akumulasi #

fold mengakumulasi semua elemen menjadi satu nilai dengan fungsi akumulator dan nilai awal. reduce mirip tapi menggunakan elemen pertama sebagai nilai awal.

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

    // fold — akumulasi dengan nilai awal
    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 untuk membangun struktur data
    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);  // "halo dunia rust"

    // reduce — seperti fold tapi tanpa nilai awal (mengembalikan 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

    // Skenario nyata: hitung frekuensi kata
    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);
    // {"apel": 3, "pisang": 2, "jeruk": 1}
}

Consumer Lainnya #

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

    // sum dan product — untuk tipe numerik
    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)

    // Untuk float (tidak implements Ord), gunakan min_by dan 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 — hitung elemen
    let jumlah_genap = v.iter().filter(|&&x| x % 2 == 0).count();
    println!("Jumlah genap: {}", jumlah_genap);  // 5

    // any dan all — pengecekan kondisi
    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 — elemen pertama yang memenuhi kondisi
    let pertama_genap = v.iter().find(|&&x| x % 2 == 0);
    println!("{:?}", pertama_genap);  // Some(2)

    // position — indeks elemen pertama yang memenuhi kondisi
    let pos = v.iter().position(|&x| x == 5);
    println!("{:?}", pos);  // Some(4)

    // for_each — seperti for loop, untuk efek samping
    v.iter().filter(|&&x| x % 2 == 0).for_each(|x| {
        println!("Genap: {}", x);
    });

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

    // nth — elemen ke-n (0-indexed), mengonsumsi sampai elemen tersebut
    let mut iter = v.iter();
    println!("{:?}", iter.nth(4));  // Some(5)
}

Iterator dari Range dan Generator #

Rust menyediakan beberapa cara untuk membuat iterator tanpa koleksi yang sudah ada.

fn main() {
    // Range — iterator bawaan Rust
    let r: Vec<i32> = (1..=5).collect();       // [1, 2, 3, 4, 5] inklusif
    let r2: Vec<i32> = (1..5).collect();        // [1, 2, 3, 4] eksklusif

    // Kombinasi range dengan 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 — mengulang satu nilai tanpa batas
    let lima_nol: Vec<i32> = std::iter::repeat(0).take(5).collect();
    println!("{:?}", lima_nol);  // [0, 0, 0, 0, 0]

    // iter::repeat_with — mengulang hasil 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 — iterator satu elemen
    let satu: Vec<i32> = std::iter::once(42).collect();
    println!("{:?}", satu);  // [42]

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

    // iter::successors — menghasilkan sekuens dari nilai sebelumnya
    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 dari fungsi generator
    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 — Implementasi Sendiri #

Membuat iterator sendiri hanya membutuhkan implementasi trait Iterator dengan method next().

// Iterator untuk deret aritmatika
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 untuk pasangan elemen berurutan (sliding window ukuran 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() {
    // Gunakan DeretAritmatika
    let deret = DeretAritmatika::new(1, 3, 20);
    let hasil: Vec<i32> = deret.collect();
    println!("{:?}", hasil);  // [1, 4, 7, 10, 13, 16, 19]

    // Semua adapter tersedia secara gratis
    let jumlah: i32 = DeretAritmatika::new(1, 2, 10).sum();
    println!("Jumlah: {}", jumlah);  // 25 (1+3+5+7+9)

    // Gunakan Pasangan
    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)]

    // Hitung selisih antar elemen berurutan
    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]
}

Perbandingan: Iterator vs Loop Manual #

Iterator dan loop for sering dianggap sebagai pilihan gaya, tapi ada perbedaan nyata dalam keterbacaan dan ekspresi niat.

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

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

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

    // Keduanya menghasilkan output yang sama
    println!("{:?}", hasil_fungsional);
    // ["Alice: 85", "Carol: 91", "Dave: 67" — tidak masuk, "Eve: 88"]

    // Kasus di mana loop lebih tepat: early return yang kompleks
    fn cari_dengan_logika_kompleks(v: &[i32]) -> Option<i32> {
        for &x in v {
            if x > 10 {
                // logika kompleks yang sulit direpresentasikan sebagai chain
                let intermediate = x * 2 - 5;
                if intermediate % 7 == 0 {
                    return Some(intermediate);
                }
            }
        }
        None
    }

    // Kasus di mana iterator lebih tepat: transformasi data yang jelas
    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 -- Ya --> C[Iterator chain]
    B -- Tidak --> D{Early return kompleks?}
    D -- Ya --> E[Loop manual]
    D -- Tidak --> F{Side effects utama?}
    F -- Ya --> G["for_each atau loop"]
    F -- Tidak --> C

    C --> C1["map, filter, fold, collect"]
    E --> E1["for loop dengan return/break"]
    G --> G1["for_each untuk efek samping"]

    style C fill:#e8f5e9
    style E fill:#fff3e0
    style G fill:#e3f2fd
Compiler Rust mengoptimalkan iterator chain dengan agresif. Benchmark menunjukkan performa iterator chain setara atau bahkan lebih baik dari loop manual untuk banyak kasus, karena LLVM dapat melakukan optimisasi seperti auto-vectorization lebih mudah pada pola iterator yang predictable.

Pola Idiomatik yang Sering Digunakan #

Beberapa pola iterator yang akan kamu temui berulang kali di kode Rust nyata.

use std::collections::HashMap;

fn main() {
    // 1. Dedup — hapus duplikat berurutan (setelah sort)
    let mut v = vec![3, 1, 2, 1, 3, 2, 1];
    v.sort();
    v.dedup();
    println!("{:?}", v);  // [1, 2, 3]

    // 2. Partisi — pisahkan menjadi dua kelompok
    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 — kelompokkan berdasarkan kunci (dengan fold ke 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': ["apel", "anggur", "alpukat"], 'p': ["pisang", "pir"]}

    // 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 dan 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 dan chunks (untuk slice)
    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 — seperti fold tapi menghasilkan setiap nilai intermediate
    let kumulatif: Vec<i32> = (1..=5)
        .scan(0, |akum, x| {
            *akum += x;
            Some(*akum)
        })
        .collect();
    println!("{:?}", kumulatif);  // [1, 3, 6, 10, 15]
}

Ringkasan #

  • Iterator bersifat lazy — adapter seperti map dan filter tidak melakukan komputasi sampai iterator dikonsumsi. Ini memungkinkan chaining panjang tanpa alokasi memori intermediate.
  • Tiga cara iterasi koleksiiter() untuk pinjam (&T), iter_mut() untuk pinjam mutable (&mut T), into_iter() untuk consume (T). Pilihan yang salah adalah sumber compile error yang umum.
  • map untuk transformasi, filter untuk penyaringan, flat_map untuk flatten — ketiganya adalah adapter paling fundamental dan paling sering digunakan.
  • fold untuk akumulasi kustom, collect untuk mengumpulkan ke koleksifold adalah consumer yang paling fleksibel; collect bisa mengumpulkan ke Vec, HashMap, HashSet, String, atau Result<Vec<T>, E>.
  • any dan all short-circuit — berhenti di elemen pertama yang menentukan hasilnya, tidak mengiterasi seluruh koleksi jika tidak perlu.
  • chain, zip, enumerate — adapter kombinasi yang sering digunakan: chain untuk menggabungkan dua iterator, zip untuk membuat pasangan, enumerate untuk menambahkan indeks.
  • Implementasikan next() untuk custom iterator — dengan mengimplementasikan satu method ini, kamu mendapat seluruh ekosistem adapter secara gratis.
  • Iterator vs loop manual — pilih iterator untuk transformasi data yang deklaratif dan komposabel; pilih loop untuk logika kompleks dengan multiple early return atau state yang sulit direpresentasikan sebagai chain.

← Sebelumnya: Option & Result   Berikutnya: Collections →

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