Sejak Chapter pertama Section Closure kita membangun fondasi: sintaks pipe, penangkapan environment, tiga trait pemanggilan, move, lalu closure sebagai parameter dan nilai kembalian. Semua fondasi itu bermuara ke satu tempat pemakaian yang paling sering kita jumpai di kode Rust sehari-hari: iterator. Metode seperti map, filter, dan sort_by_key menerima closure dari kita — aturan transformasi, syarat seleksi, kunci pengurutan — lalu menerapkannya pada setiap elemen data. Chapter ini adalah jembatan: kita melihat closure bekerja di atas tiga adapter iterator paling populer dengan data sederhana, tanpa menyelami mekanisme iterator itu sendiri. Mekanisme lengkapnya — trait Iterator, lazy evaluation, dan keluarga adapter yang jauh lebih besar — menunggu kita di Section Iterator yang datang tepat setelah ini.
Masalah: Transformasi Data Tanpa Loop Manual #
Kita mulai dari masalah yang pasti akrab. Data harga menu tersimpan dalam array, dan kita perlu menaikkan seluruh harga sebesar Rp2.000 untuk katalog baru. Dengan peralatan sejauh ini, solusinya adalah loop manual: buat Vec kosong, ulangi setiap elemen, hitung nilai baru, lalu push hasilnya:
fn main() {
let harga_jus = [18000, 22000, 25000];
let mut harga_baru = Vec::new();
for harga in harga_jus.iter() {
harga_baru.push(harga + 2000);
}
println!("Harga baru: {:?}", harga_baru);
}Output yang Dihasilkan:
Harga baru: [20000, 24000, 27000]Kode ini benar, tetapi tiga baris mekanis — wadah sementara, loop, push — mengaburkan niat sebenarnya: naikkan setiap harga sebesar 2.000. Adapter map memindahkan niat itu ke satu baris.
Adapter map #
Metode map menerima closure dan mengembalikan iterator baru yang menerapkan closure pada setiap elemen:
fn main() {
let harga_jus = [18000, 22000, 25000];
let harga_baru: Vec<u64> = harga_jus.iter().map(|harga| harga + 2000).collect();
println!("Harga lama: {:?}", harga_jus);
println!("Harga baru: {:?}", harga_baru);
}Output yang Dihasilkan:
Harga lama: [18000, 22000, 25000]
Harga baru: [20000, 24000, 27000]Hasilnya identik dengan loop manual — harga_baru berisi tiga harga yang sudah dinaikkan. Mari kita bedah versi map:
- Baris 3 (
harga_jus.iter()): pemanggilaniter()meminjam array sebagai iterator yang memuntahkan elemen satu per satu dalam bentuk reference&u64— pola pinjaman yang sudah kita kenal, array aslinya tetap utuh dan tercetak di baris berikutnya. - Baris 3 (
.map(|harga| harga + 2000)): closure|harga| harga + 2000menjadi aturan transformasi. Parameterhargamenerima setiap&u64dari iterator, dan ekspresiharga + 2000menghasilkanu64baru — closure ini tidak menangkap apa pun, persis closureFndari Chapter Fn, FnMut, FnOnce. - Baris 3 (
.collect()):collect()menjalankan seluruh pipeline dan mengumpulkan hasilnya menjadi koleksi. AnotasiVec<u64>di sisi kiri memberitahucollectbentuk koleksi yang kita inginkan — mekanisme penuhnya ada di Section Iterator. - Perbandingan dengan loop manual: tiga baris mekanis menyusut jadi satu rantai. Wadah
mut, loopfor, danpushmenghilang; yang tersisa hanya deklarasi data dan aturan transformasinya.
Adapter filter dan Rantai Adapter #
filter menerima closure yang mengembalikan bool sebagai syarat seleksi: elemen dengan hasil true lolos, sisanya dibuang. Kekuatan sebenarnya muncul saat adapter dirantai, dan di sinilah kemampuan menangkap environment kita terpakai — closure filter berikut memakai batas dari scope sekitarnya:
fn main() {
let harga_menu = [18000, 35000, 12000, 42000, 26000];
let batas = 25000;
let menu_terjangkau: Vec<u64> = harga_menu
.iter()
.filter(|harga| **harga <= batas)
.map(|harga| harga + 1000)
.collect();
println!("Menu di bawah Rp{}: {:?}", batas, menu_terjangkau);
}Output yang Dihasilkan:
Menu di bawah Rp25000: [19000, 13000]Mari kita bedah rantai ini:
- Baris 7 (
.filter(|harga| **harga <= batas)): closurefiltermenerima tiap elemen dan mengembalikanbool. Perhatikan dua tanda bintang pada**harga:iter()memuntahkan&u64, lalufiltermembungkus elemennya sekali lagi menjadi&&u64karenafilterbutuh memeriksa hasilboolsebelum memutuskan nasib elemen — tanda bintang pertama melepas bungkusfilter, tanda bintang kedua melepas referenceiter(). - Baris 7 (penangkapan
batas): closure membandingkan**hargadenganbatas— variabel dari scopemainyang ditangkap sebagai reference immutable. Inilah closure bertugas sungguhan: syarat seleksinya tidak dikirim sebagai argumen, melainkan dibawa dari lingkungan sekitar. Menggantibatasdi satu tempat mengubah perilaku seluruh rantai. - Baris 8 (
.map(|harga| harga + 1000)): elemen yang lolos seleksifilteradalah 18000 dan 12000 — 26000 gagal karena melebihibatas. Adaptermapberikutnya menaikkan masing-masing sebesar 1000 menjadi 19000 dan 13000. - Baris 5–9 (urutan eksekusi): rantai dibaca dari atas ke bawah — saring dulu, transformasi belakangan. Urutan ini menentukan hasil: membalik
filterdanmapakan menaikkan seluruh harga dulu sehingga 24000 lolos seleksi. Menyusun urutan adapter adalah keputusan desain, bukan sekadar gaya penulisan.
sort_by_key dan Kerabat Lainnya #
Keluarga adapter iterator berjalan juga di dalam metode koleksi. sort_by_key pada Vec menerima closure yang menentukan kunci pengurutan — closure mengembalikan nilai yang bisa dibandingkan, dan elemen diurutkan berdasarkan nilai tersebut:
fn main() {
let mut pesanan = vec![
("Siti Rahma", 7),
("Budi Santoso", 1),
("Rina Wijaya", 4),
];
pesanan.sort_by_key(|item| item.1);
println!("{:?}", pesanan);
}Output yang Dihasilkan:
[("Budi Santoso", 1), ("Rina Wijaya", 4), ("Siti Rahma", 7)]Mari kita bedah:
- Baris 2–6 (
let mut pesanan = vec![...]):Vecberisi tuple(&str, u64)— nama pelanggan dan jumlah item pesanannya.Vecdanvec!sudah kita kenal di Section Koleksi;mutdibutuhkan karenasort_by_keymengatur ulang isi di tempat. - Baris 8 (
pesanan.sort_by_key(|item| item.1)): closure|item| item.1menerima tiap tuple dan mengembalikanitem.1— angka jumlah item — sebagai kunci urutan. Hasilnya: pesanan tersusun dari jumlah terkecil ke terbesar. Ganti kuncinya menjadiitem.0dan urutan berubah menjadi alfabetis berdasarkan nama. - Baris 9 (
println!("{:?}", pesanan);): tuple mengimplementasikanDebug, sehingga format{:?}mencetak seluruh isiVec— termasuk tanda kutip dan kurung yang terlihat pada output.
Polanya sama di seluruh pustaka standar: metode yang “butuh pendapat kita” meminta pendapat itu lewat closure. retain menyaring isi koleksi di tempat dengan closure bool, find mengembalikan elemen pertama yang cocok dengan closure, dan keluarga besar adapter iterator lainnya menanti di Section Iterator.
Menuju Section Iterator #
Chapter ini sengaja menahan diri pada tiga pemakaian closure yang paling umum. Di balik map dan filter ada satu trait Iterator dengan satu method wajib next; adapter hanyalah method yang membangun iterator baru di atas iterator lama; dan lazy evaluation membuat rantai panjang tetap hemat karena elemen diproses satu per satu tanpa koleksi perantara. Semua mekanisme itu, ditambah adapter seperti fold, zip, enumerate, dan take, adalah wilayah Section Iterator — dan kita masuki wilayah itu dengan bekal lengkap: closure di tangan, trait di kepala.
Ringkasan #
Closure menemukan panggung terbesarnya di iterator: map menerima aturan transformasi per elemen, filter menerima syarat seleksi bool — seraya menangkap variabel seperti batas dari lingkungan sekitar — dan sort_by_key menerima kunci pengurutan. Rantai adapter menggantikan loop manual for + push dengan deklarasi berurutan yang terbaca seperti niatnya, dan collect() memicu eksekusi seluruh rantai. Section berikutnya membuka mesin di balik semua ini: trait Iterator, lazy evaluation, dan keluarga adapter yang jauh lebih besar dari yang kita intip di sini.