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:
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:
- Baris 1 (
fn filter<P>(self, predicate: P) -> Filter<Self, P>): iterator sumber dimiliki penuh oleh pembungkusFilter<Self, P>, dan hasilnya tetap ber-tipeSelf::Item—filtertidak pernah mengubah jenis elemen, hanya memilih elemen mana yang lolos. - Baris 3 (
P: FnMut(&Self::Item) -> bool): predicate menerima reference ke elemen (&Self::Item) dan mengembalikanbool. Nilaitrueberarti elemen lolos;falseberarti 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:
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:
Menu premium: Rp17500
Menu premium: Rp20000Mari kita bedah baris demi baris:
- Baris 2 (
let prices = vec![12000, 15000, 17500, 20000];): koleksi sumber berisi empat harga. Karena kita memakaiinto_iter(), kepemilikan elemen akan berpindah ke pipeline — pilihan yang pas di sini karenapricestidak diperlukan lagi setelah penyaringan. - Baris 4 (
prices.into_iter().filter(|price| *price > 15000)): iteratorinto_iter()menyerahkani32per elemen, lalufiltermembungkusnya. Predicate|price| *price > 15000menerima&i32— sesuai&Self::Itempada signature — sehingga tanda*diperlukan untuk membandingkan angka di balik reference. Elemen12000dan15000gagal kriteria,17500dan20000lolos. - Baris 5 (
println!("Menu premium: Rp{}", price);): badanforhanya berjalan dua kali karena hanya dua elemen yang sampai ke consumer. Dua elemen gagal saringan tidak pernah menginjak badan perulangan — keduanya dijatuhkan begitu predicate mengembalikanfalse.
Perhatikan tanda * pada *price — titik ini sering menjegal pemula, jadi kita buktikan betapa pentingnya dengan sengaja menghilangkannya:
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:
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:
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:
Tersedia: Kopi Gayo
Tersedia: Gula ArenUraian baris-per-baris:
- Baris 7-11 (
let products = vec![...]): koleksi berisi tigaProduct; pemanggilaniter()membuat pipeline hanya meminjam, sehinggaproductstetap hidup setelah perulangan. - Baris 13 (
products.iter().filter(|product| product.stock > 0)): iterator sumber menyerahkan&Product, sehingga predicate menerima&&Product— reference ke reference. Aksesproduct.stocktetap berjalan mulus karena operator akses field.melakukan dereference berlapis secara otomatis; di sinilah keunggulan struct dibanding angka polos pada contoh pertama. - Baris 14 (
println!("Tersedia: {}", available.name);):availablebertipe&Product— elemen yang lolos diteruskan apa adanya ke consumer.Teh Melatidengan stok0tidak 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:
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:
Lolos saringan: Rp17500
Lolos saringan: Rp20000
Total elemen diperiksa: 4Mari kita bedah bagian pentingnya:
- Baris 3 (
let mut checked_count = 0;): variabel penghitung dideklarasikanmutdi scopemainkarena closure akan mengubah nilainya secara berulang. - Baris 5-8 (
filter(|price| { ... })): predicate kini berbadan multi-baris. Baris pertama menaikkanchecked_count— akses tulis ke variabel tangkapan inilah yang menuntut boundFnMut. Baris terakhir tanpa titik koma,*price > 15000, menjadi nilai kembalianbool. - Baris 12 (
println!("Total elemen diperiksa: ...")): hasil4membuktikan 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.