Tidak semua elemen koleksi layak diproses lebih jauh. Menu premium hanya memuat harga di atas ambang tertentu, daftar tampilan hanya boleh berisi produk yang stoknya tersedia, dan laporan penjualan hanya menampilkan transaksi di atas nilai minimum. Dengan perulangan manual, penyaringan berarti if di dalam for — pola yang bekerja, tetapi mencampur dua keputusan (kriteria seleksi dan mekanisme perulangan) dalam satu blok kode. Adapter filter memisahkannya: kita cukup menyatakan kriteria seleksi sebagai sebuah predicate — closure yang mengembalikan bool — dan filter akan menyerahkan hanya elemen yang lolos ke tahap berikutnya. Chapter ini membedah cara kerja filter, konsekuensi tanda & pada parameternya, serta perilakunya yang tetap malas seperti semua adapter.

filter dan Predicate-nya #

filter adalah adapter pembungkus, sama seperti map: menerima iterator, mengembalikan iterator baru. Signature aslinya di pustaka standar tampak seperti ini:

rust
fn filter<P>(self, predicate: P) -> Filter<Self, P>
where
    P: FnMut(&Self::Item) -> bool,

Dua detail pada tanda tangan ini membedakan filter dari map:

  1. Baris 1 (fn filter<P>(self, predicate: P) -> Filter<Self, P>): iterator sumber dimiliki penuh oleh pembungkus Filter<Self, P>, dan hasilnya tetap ber-tipe Self::Item — filter tidak pernah mengubah jenis elemen, hanya memilih elemen mana yang lolos.
  2. Baris 3 (P: FnMut(&Self::Item) -> bool): predicate menerima reference ke elemen (&Self::Item) dan mengembalikan bool. Nilai true berarti elemen lolos; false berarti elemen dibuang untuk putaran tarikan saat ini.

Mengapa predicate hanya menerima reference? Karena tugas predicate semata-mata memeriksa, bukan memiliki: elemen yang lolos harus tetap utuh untuk diteruskan ke consumer, sehingga predicate tidak boleh memakannya. Reference pada parameter adalah penegakan langsung aturan ini oleh tanda tangan method.

Contoh Dasar: Menyaring Harga Premium #

Toko kopi kita ingin menampilkan menu premium dengan harga di atas Rp15.000. Contoh berikut memakai into_iter() agar setiap elemen berupa i32 utuh:

rust
fn main() {
    let prices = vec![12000, 15000, 17500, 20000];

    for price in prices.into_iter().filter(|price| *price > 15000) {
        println!("Menu premium: Rp{}", price);
    }
}

Output yang Dihasilkan:

text
Menu premium: Rp17500
Menu premium: Rp20000

Mari kita bedah baris demi baris:

  1. Baris 2 (let prices = vec![12000, 15000, 17500, 20000];): koleksi sumber berisi empat harga. Karena kita memakai into_iter(), kepemilikan elemen akan berpindah ke pipeline — pilihan yang pas di sini karena prices tidak diperlukan lagi setelah penyaringan.
  2. Baris 4 (prices.into_iter().filter(|price| *price > 15000)): iterator into_iter() menyerahkan i32 per elemen, lalu filter membungkusnya. Predicate |price| *price > 15000 menerima &i32 — sesuai &Self::Item pada signature — sehingga tanda * diperlukan untuk membandingkan angka di balik reference. Elemen 12000 dan 15000 gagal kriteria, 17500 dan 20000 lolos.
  3. Baris 5 (println!("Menu premium: Rp{}", price);): badan for hanya berjalan dua kali karena hanya dua elemen yang sampai ke consumer. Dua elemen gagal saringan tidak pernah menginjak badan perulangan — keduanya dijatuhkan begitu predicate mengembalikan false.

Perhatikan tanda * pada *price — titik ini sering menjegal pemula, jadi kita buktikan betapa pentingnya dengan sengaja menghilangkannya:

rust
fn main() {
    let prices = vec![12000, 15000, 17500, 20000];

    for price in prices.into_iter().filter(|price| price > 15000) {
        println!("Menu premium: Rp{}", price);
    }
}

Kompilator menolak dengan pesan galat asli berikut:

text
error[E0308]: mismatched types
 --> main.rs:4:60
  |
4 |     for price in prices.into_iter().filter(|price| price > 15000) {
  |                                                            ^^^^^ expected `&_`, found integer
  |
  = note: expected reference `&_`
                  found type `{integer}`
help: consider dereferencing the borrow
  |
4 |     for price in prices.into_iter().filter(|price| *price > 15000) {
  |                                                    +

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0308`.

Pesan E0308 menegaskan apa yang sudah dibaca dari signature: operator > membandingkan &i32 di sisi kiri dengan angka polos di sisi kanan, dan Rust tidak menyediakan kombinasi pembanding tersebut. Kompilator bahkan menuliskan perbaikannya — tambahkan * sebelum price. Kebiasaan membaca parameter predicate sebagai “reference ke elemen” membuat pesan seperti ini langsung masuk akal.

filter pada Koleksi Struct #

Penyaringan di dunia nyata hampir selalu tentang field dari struct. Gudang kita menyimpan daftar Product lengkap dengan jumlah stok, dan halaman katalog hanya boleh menampilkan produk yang tersedia:

rust
struct Product {
    name: String,
    stock: i32,
}

fn main() {
    let products = vec![
        Product { name: String::from("Kopi Gayo"), stock: 12 },
        Product { name: String::from("Teh Melati"), stock: 0 },
        Product { name: String::from("Gula Aren"), stock: 5 },
    ];

    for available in products.iter().filter(|product| product.stock > 0) {
        println!("Tersedia: {}", available.name);
    }
}

Output yang Dihasilkan:

text
Tersedia: Kopi Gayo
Tersedia: Gula Aren

Uraian baris-per-baris:

  1. Baris 7-11 (let products = vec![...]): koleksi berisi tiga Product; pemanggilan iter() membuat pipeline hanya meminjam, sehingga products tetap hidup setelah perulangan.
  2. Baris 13 (products.iter().filter(|product| product.stock > 0)): iterator sumber menyerahkan &Product, sehingga predicate menerima &&Product — reference ke reference. Akses product.stock tetap berjalan mulus karena operator akses field . melakukan dereference berlapis secara otomatis; di sinilah keunggulan struct dibanding angka polos pada contoh pertama.
  3. Baris 14 (println!("Tersedia: {}", available.name);): available bertipe &Product — elemen yang lolos diteruskan apa adanya ke consumer. Teh Melati dengan stok 0 tidak pernah sampai baris ini.

Contoh ini menegaskan kembali pembagian kerja adapter: filter memilih elemen tanpa mengubah bentuknya, sehingga tipe yang diterima badan for sama persis dengan tipe yang dikeluarkan sumber.

Predicate dengan State #

Predicate bertipe FnMut, yang berarti closure boleh mengubah state yang ditangkapnya dari pemanggilan ke pemanggilan. Kita bisa memakai sifat ini untuk menghitung berapa banyak elemen yang diperiksa predicate — angka yang membuktikan predicate benar-benar berjalan sekali per elemen:

rust
fn main() {
    let prices = vec![12000, 15000, 17500, 20000];
    let mut checked_count = 0;

    for price in prices.into_iter().filter(|price| {
        checked_count = checked_count + 1;
        *price > 15000
    }) {
        println!("Lolos saringan: Rp{}", price);
    }

    println!("Total elemen diperiksa: {}", checked_count);
}

Output yang Dihasilkan:

text
Lolos saringan: Rp17500
Lolos saringan: Rp20000
Total elemen diperiksa: 4

Mari kita bedah bagian pentingnya:

  1. Baris 3 (let mut checked_count = 0;): variabel penghitung dideklarasikan mut di scope main karena closure akan mengubah nilainya secara berulang.
  2. Baris 5-8 (filter(|price| { ... })): predicate kini berbadan multi-baris. Baris pertama menaikkan checked_count — akses tulis ke variabel tangkapan inilah yang menuntut bound FnMut. Baris terakhir tanpa titik koma, *price > 15000, menjadi nilai kembalian bool.
  3. Baris 12 (println!("Total elemen diperiksa: ...")): hasil 4 membuktikan predicate dipanggil tepat empat kali — sekali untuk setiap elemen, termasuk dua elemen yang gagal. Tidak ada elemen yang diperiksa dua kali, dan tidak ada yang lolos tanpa diperiksa.

Ringkasan #

Adapter filter menerima predicate FnMut(&Self::Item) -> bool dan menghasilkan iterator yang hanya menyerahkan elemen dengan hasil true; tipe elemen tidak pernah berubah, dan elemen yang lolos diteruskan utuh tanpa pernah dimiliki predicate. Predicate menerima elemen lewat reference — &Self::Item — sehingga pada elemen angka kita perlu dereference dengan *, dan kelupaan tanda tersebut ditangkap kompilator sebagai E0308 lengkap dengan saran perbaikan. Pada struct, akses field bekerja otomatis meskipun reference berlapis. Dengan map untuk mengubah dan filter untuk menyaring di tangan, kita siap menyusun keduanya menjadi satu pipeline bertingkat pada chapter berikutnya.