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
}
| Method | Menghasilkan | Ownership | Koleksi setelahnya |
|---|---|---|---|
iter() | &T | Pinjam | Masih valid |
iter_mut() | &mut T | Pinjam mutable | Masih valid, sudah dimodifikasi |
into_iter() | T | Consume | Tidak 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 hasilAdapter — 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:#e3f2fdCompiler 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
mapdanfiltertidak melakukan komputasi sampai iterator dikonsumsi. Ini memungkinkan chaining panjang tanpa alokasi memori intermediate.- Tiga cara iterasi koleksi —
iter()untuk pinjam (&T),iter_mut()untuk pinjam mutable (&mut T),into_iter()untuk consume (T). Pilihan yang salah adalah sumber compile error yang umum.mapuntuk transformasi,filteruntuk penyaringan,flat_mapuntuk flatten — ketiganya adalah adapter paling fundamental dan paling sering digunakan.folduntuk akumulasi kustom,collectuntuk mengumpulkan ke koleksi —foldadalah consumer yang paling fleksibel;collectbisa mengumpulkan keVec,HashMap,HashSet,String, atauResult<Vec<T>, E>.anydanallshort-circuit — berhenti di elemen pertama yang menentukan hasilnya, tidak mengiterasi seluruh koleksi jika tidak perlu.chain,zip,enumerate— adapter kombinasi yang sering digunakan:chainuntuk menggabungkan dua iterator,zipuntuk membuat pasangan,enumerateuntuk 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.