map dan filter melayani satu iterator pada satu waktu, tetapi kebutuhan data nyata sering melibatkan lebih dari satu rangkaian: daftar nama produk di satu Vec, daftar harga di Vec lain, dan kita ingin keduanya berjalan beriringan. Kadang kita butuh nomor urut tanpa repot mengelola penghitung sendiri, kadang perlu menggabungkan dua daftar menjadi satu, membalik urutan, atau mengambil sebagian elemen untuk kebutuhan halaman. Pustaka standar Rust menyediakan adapter bawaan untuk semua kebutuhan tersebut — zip, enumerate, chain, rev, take, dan skip — yang semuanya mengikuti aturan main yang sudah kita kuasai: membungkus iterator menjadi iterator baru, dan tetap malas sampai consumer menarik. Chapter ini menjelajahinya satu per satu, lalu menutup dengan perangkaian semuanya dalam satu pipeline.
zip: Menjodohkan Dua Iterator #
Adapter zip menerima dua iterator dan menghasilkan iterator baru yang elemennya berupa tuple — pasangan nilai yang diambil berdampingan dari kedua sumber. Toko kita menyimpan nama dan harga dalam dua Vec terpisah, dan daftar menu membutuhkan keduanya:
fn main() {
let names = vec!["Kopi Gayo", "Teh Melati", "Gula Aren"];
let prices = vec![15000, 8000, 5000, 25000];
for (name, price) in names.iter().zip(prices.iter()) {
println!("{} - Rp{}", name, price);
}
}Output yang Dihasilkan:
Kopi Gayo - Rp15000
Teh Melati - Rp8000
Gula Aren - Rp5000Mari kita bedah baris demi baris:
- Baris 2-3 (
let names = ...; let prices = ...): dua koleksi sumber dengan panjang berbeda — tiga nama, empat harga. Ketidakseimbangan ini disengaja untuk membuktikan satu perilaku penting. - Baris 5 (
for (name, price) in names.iter().zip(prices.iter())):zipmemasangkan elemen pertama dengan pertama (“Kopi Gayo”, 15000), kedua dengan kedua, dan seterusnya. Setiap elemen pasangan dikemas sebagai tuple(&&str, &i32); pola(name, price)padaforlangsung membedah tuple tersebut — teknik destructuring dari section Pattern Matching. - Baris 6 (
println!("{} - Rp{}", name, price);): badan perulangan menerima kedua nilai hasil pasangan tanpa pernah menyentuh indeks. - Harga
25000yang keempat: nilai ini tidak pernah muncul di output.zipberhenti begitu salah satu sumber habis — tiga pasangan terbentuk, elemen keempatpricesdibiarkan tidak terpakai. Aturan pihak yang lebih pendek menang berlaku selalu.
enumerate: Nomor Urut Otomatis #
Kita berulang kali menulis pola “cetak nomor urut lalu isi”. Cara manual membutuhkan variabel penghitung yang dinaikkan sendiri di setiap putaran; enumerate menghapus seluruh ritual itu dengan membungkus iterator menjadi pasangan (index, item):
fn main() {
let menu = vec!["Kopi Gayo", "Teh Melati", "Gula Aren"];
for (index, name) in menu.iter().enumerate() {
println!("{}. {}", index + 1, name);
}
}Output yang Dihasilkan:
1. Kopi Gayo
2. Teh Melati
3. Gula ArenUraian baris-per-baris:
- Baris 4 (
for (index, name) in menu.iter().enumerate()):enumeratemenghasilkan tuple(usize, &str)per elemen —indexdimulai dari nol sesuai konvensi Rust. Pola(index, name)membedah tuple di posisifor, sehingga kedua nilai langsung tersedia. - Baris 5 (
println!("{}. {}", index + 1, name);): kita menambah satu untuk menampilkan nomor mulai dari 1 — tampilan ramah pembaca.enumeratesendiri tidak peduli bagaimana nomor dipakai; tugasnya hanya menyediakan angkanya. - Posisi enumerate dalam rantai — satu detail yang akan terasa penting pada contoh terakhir chapter ini:
enumeratememberi nomor berdasarkan posisi elemen saat elemen melewatienumerate, bukan posisi aslinya di koleksi. Menempatkanenumeratesebelum atau sesudah adapter lain menghasilkan penomoran yang berbeda.
rev dan chain: Membalik dan Menyambung #
Dua adapter berikut menjawab kebutuhan bentuk. chain menyambung iterator kedua setelah iterator pertama habis; rev membalik arah iterasi:
fn main() {
let morning_menu = vec!["Kopi Gayo", "Teh Melati"];
let evening_menu = vec!["Wedang Jahe", "Cokelat Panas"];
println!("Urutan maju:");
for name in morning_menu.iter().chain(evening_menu.iter()) {
println!(" {}", name);
}
let closing = vec!["Gula Aren", "Kopi Toraja"];
println!("Urutan mundur:");
for name in closing.iter().rev() {
println!(" {}", name);
}
}Output yang Dihasilkan:
Urutan maju:
Kopi Gayo
Teh Melati
Wedang Jahe
Cokelat Panas
Urutan mundur:
Kopi Toraja
Gula ArenMari kita bedah bagian pentingnya:
- Baris 6 (
morning_menu.iter().chain(evening_menu.iter())):chainmengambil iterator lain sebagai argumen. Saat dikonsumsi, elemenmorning_menukeluar lebih dulu sampai habis, baru elemenevening_menumenyusul — duaVecterpisah dijelajahi sebagai satu rangkaian empat elemen tanpa koleksi gabungan baru. - Baris 12 (
closing.iter().rev()):revmenghasilkan iterator yang menarik elemen dari belakang ke depan. Kedua koleksi tetap tidak tersentuh — pembalikan terjadi pada arah penarikan, bukan pada data.
Satu catatan teknis untuk rev: method ini hanya tersedia bagi iterator yang tahu cara berjalan dua arah — kategori yang di pustaka standar disebut DoubleEndedIterator. Iterator Vec termasuk kategori tersebut; iterator seperti hasil chain pada contoh juga termasuk. Sebagian iterator lain — misalnya yang membaca data dari jaringan — hanya bisa maju, dan rev tidak akan tersedia untuk mereka.
take dan skip: Mengambil Sebagian #
Adapter take(n) memotong iterator setelah n elemen; skip(n) melewatkan n elemen pertama lalu meneruskan sisanya. Kombinasi keduanya adalah resep klasik paginasi — menampilkan data per halaman:
fn main() {
let menu = vec![
"Kopi Gayo",
"Teh Melati",
"Gula Aren",
"Kopi Toraja",
"Cokelat Panas",
];
let page = 2;
let per_page = 2;
let start = (page - 1) * per_page;
for (index, name) in menu.iter().enumerate().skip(start).take(per_page) {
println!("{}. {}", index + 1, name);
}
}Output yang Dihasilkan:
3. Gula Aren
4. Kopi TorajaUraian baris-per-baris:
- Baris 10-12 (
let page = 2; let per_page = 2; let start = ...): halaman ke-2 dengan dua item per halaman berarti melewati dua elemen pertama — hitungan(page - 1) * per_page. - Baris 14 (
menu.iter().enumerate().skip(start).take(per_page)): rantai dibaca dari sumber —enumeratememberi nomor 0 sampai 4,skip(2)membuang nomor 0 dan 1,take(2)menghentikan aliran setelah dua elemen berikutnya. Perhatikan urutan ini: karenaenumerateberdiri sebelumskip, nomor yang lolos tetap nomor asli —2dan3— sehingga tampilan pengguna tetap melihat posisi sebenarnya (elemen ke-3 dan ke-4 setelah ditambah satu). - Efek samping laziness —
take(2)membuat elemen kelimaCokelat Panastidak pernah ditarik sama sekali. Pada data lima elemen efeknya kecil, tetapi pada pipeline yang mahal, memotong di depan berarti seluruh pekerjaan untuk elemen yang tidak diambil tidak pernah terjadi.
Merangkai Semuanya #
Penutup chapter ini merangkai tiga adapter sekaligus: zip menjodohkan nama dan harga, enumerate memberi nomor, take membatasi jumlah baris tampilan:
fn main() {
let names = vec!["Kopi Gayo", "Teh Melati", "Gula Aren", "Kopi Toraja"];
let prices = vec![15000, 8000, 5000, 17500];
for (index, (name, price)) in names.iter().zip(prices.iter()).enumerate().take(3) {
println!("{}. {} - Rp{}", index + 1, name, price);
}
}Output yang Dihasilkan:
1. Kopi Gayo - Rp15000
2. Teh Melati - Rp8000
3. Gula Aren - Rp5000Mari kita bedah baris kuncinya:
- Baris 5 (
for (index, (name, price)) in ...): pola bersarang(index, (name, price))membedah tuple bertingkat — lapisan luar milikenumerate, lapisan dalam milikzip. Setiap lapisan adapter menyumbang satu tingkat kedalaman pola. - Baris 5 (urutan
.zip(...).enumerate().take(3)):zipmenghasilkan pasangan;enumeratemembungkus pasangan menjadi bernomor;take(3)memotong setelah tiga pasangan. Menu keempatKopi Torajatidak pernah sampai ke consumer. - Baris 6 (
println!("{}. {} - Rp{}", ...)): badanformenerima tiga nilai terurai — nomor, nama, harga — seolah semuanya datang dari satu struktur data yang rapi, padahal sumbernya tiga lapis iterator yang disusun bertumpuk.
Ringkasan #
Adapter bawaan melampaui map dan filter: zip menjodohkan dua iterator menjadi tuple pasangan dan berhenti di sisi yang lebih pendek, enumerate menyediakan nomor urut otomatis yang posisinya mengikuti tempatnya dalam rantai, chain menyambung dua rangkaian menjadi satu, rev membalik arah penarikan bagi iterator dua arah, serta take dan skip memotong dan melewatkan elemen — resep paginasi yang hanya memproses apa yang ditampilkan. Semua mengikuti hukum yang sama: membungkus, tetap malas, dan siap dirangkai sampai kedalaman berapa pun dengan pola destructuring yang mengikuti bentuk lapisannya. Kotak peralatan iterator kini tinggal satu tingkat lagi menuju puncak: menjadikan tipe buatan kita sendiri sebagai iterator sungguhan.