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:

rust
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:

text
Kopi Gayo - Rp15000
Teh Melati - Rp8000
Gula Aren - Rp5000

Mari kita bedah baris demi baris:

  1. 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.
  2. Baris 5 (for (name, price) in names.iter().zip(prices.iter())): zip memasangkan elemen pertama dengan pertama (“Kopi Gayo”, 15000), kedua dengan kedua, dan seterusnya. Setiap elemen pasangan dikemas sebagai tuple (&&str, &i32); pola (name, price) pada for langsung membedah tuple tersebut — teknik destructuring dari section Pattern Matching.
  3. Baris 6 (println!("{} - Rp{}", name, price);): badan perulangan menerima kedua nilai hasil pasangan tanpa pernah menyentuh indeks.
  4. Harga 25000 yang keempat: nilai ini tidak pernah muncul di output. zip berhenti begitu salah satu sumber habis — tiga pasangan terbentuk, elemen keempat prices dibiarkan 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):

rust
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:

text
1. Kopi Gayo
2. Teh Melati
3. Gula Aren

Uraian baris-per-baris:

  1. Baris 4 (for (index, name) in menu.iter().enumerate()): enumerate menghasilkan tuple (usize, &str) per elemen — index dimulai dari nol sesuai konvensi Rust. Pola (index, name) membedah tuple di posisi for, sehingga kedua nilai langsung tersedia.
  2. Baris 5 (println!("{}. {}", index + 1, name);): kita menambah satu untuk menampilkan nomor mulai dari 1 — tampilan ramah pembaca. enumerate sendiri tidak peduli bagaimana nomor dipakai; tugasnya hanya menyediakan angkanya.
  3. Posisi enumerate dalam rantai — satu detail yang akan terasa penting pada contoh terakhir chapter ini: enumerate memberi nomor berdasarkan posisi elemen saat elemen melewati enumerate, bukan posisi aslinya di koleksi. Menempatkan enumerate sebelum 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:

rust
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:

text
Urutan maju:
  Kopi Gayo
  Teh Melati
  Wedang Jahe
  Cokelat Panas
Urutan mundur:
  Kopi Toraja
  Gula Aren

Mari kita bedah bagian pentingnya:

  1. Baris 6 (morning_menu.iter().chain(evening_menu.iter())): chain mengambil iterator lain sebagai argumen. Saat dikonsumsi, elemen morning_menu keluar lebih dulu sampai habis, baru elemen evening_menu menyusul — dua Vec terpisah dijelajahi sebagai satu rangkaian empat elemen tanpa koleksi gabungan baru.
  2. Baris 12 (closing.iter().rev()): rev menghasilkan 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:

rust
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:

text
3. Gula Aren
4. Kopi Toraja

Uraian baris-per-baris:

  1. 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.
  2. Baris 14 (menu.iter().enumerate().skip(start).take(per_page)): rantai dibaca dari sumber — enumerate memberi nomor 0 sampai 4, skip(2) membuang nomor 0 dan 1, take(2) menghentikan aliran setelah dua elemen berikutnya. Perhatikan urutan ini: karena enumerate berdiri sebelum skip, nomor yang lolos tetap nomor asli — 2 dan 3 — sehingga tampilan pengguna tetap melihat posisi sebenarnya (elemen ke-3 dan ke-4 setelah ditambah satu).
  3. Efek samping laziness — take(2) membuat elemen kelima Cokelat Panas tidak 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:

rust
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:

text
1. Kopi Gayo - Rp15000
2. Teh Melati - Rp8000
3. Gula Aren - Rp5000

Mari kita bedah baris kuncinya:

  1. Baris 5 (for (index, (name, price)) in ...): pola bersarang (index, (name, price)) membedah tuple bertingkat — lapisan luar milik enumerate, lapisan dalam milik zip. Setiap lapisan adapter menyumbang satu tingkat kedalaman pola.
  2. Baris 5 (urutan .zip(...).enumerate().take(3)): zip menghasilkan pasangan; enumerate membungkus pasangan menjadi bernomor; take(3) memotong setelah tiga pasangan. Menu keempat Kopi Toraja tidak pernah sampai ke consumer.
  3. Baris 6 (println!("{}. {} - Rp{}", ...)): badan for menerima 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.