Sepanjang section ini kita menjadi konsumen setia iterator: memakai iter(), merangkai map, menyaring dengan filter, lalu menagih hasil lewat collect. Sekarang saatnya membuka dapurnya. Semua method tersebut sesungguhnya hanya berdiri di atas satu method next — yang berarti tipe buatan kita sendiri dapat masuk ke seluruh ekosistem iterator cukup dengan mengimplementasikan trait Iterator. Begitu kontrak mini itu terpenuhi, for, map, filter, zip, enumerate, hingga sum dan collect tersedia gratis — tanpa satu baris pun kode tambahan dari kita. Pada chapter penutup ini kita bangun iterator klasik dari komunitas Rust bernama Counter, lalu membuktikan bahwa iterator buatan sendiri masuk mulus ke pipeline paling rumit sekalipun.
Rangkaian yang Tidak Tinggal di Koleksi #
Mari kita mulai dari masalah yang tidak bisa dijawab Vec. Program poin keanggotaan toko kita mengenal lima tingkat: nilai poin tiap tingkat adalah kelipatan sepuluh — 10, 20, 30, 40, 50. Nilai berpola seperti ini bisa dihitung bertahap; nilai tersebut tidak harus tinggal di koleksi mana pun sebelum benar-benar dibutuhkan. Kebutuhan semacam ini — “sumber nilai yang tahu cara menghasilkan elemen berikutnya” — adalah wilayah iterator, dan kita akan menyediakannya lewat struct Counter yang menyimpan satu field: posisi hitungan saat ini.
Implementasi Trait Iterator untuk Counter #
Berikut definisi lengkap Counter beserta implementasi trait-nya:
struct Counter {
count: u32,
}
impl Counter {
fn new() -> Counter {
Counter { count: 0 }
}
}
impl Iterator for Counter {
type Item = u32;
fn next(&mut self) -> Option<u32> {
if self.count < 5 {
self.count = self.count + 1;
Some(self.count)
} else {
None
}
}
}
fn main() {
let mut counter = Counter::new();
println!("Hasil ke-1: {:?}", counter.next());
println!("Hasil ke-2: {:?}", counter.next());
println!("Hasil ke-3: {:?}", counter.next());
}Output yang Dihasilkan:
Hasil ke-1: Some(1)
Hasil ke-2: Some(2)
Hasil ke-3: Some(3)Mari kita bedah baris demi baris:
- Baris 1-3 (
struct Counter { count: u32 }): struct hanya menyimpan satu field bernamacount— kondisi internal iterator. Tidak adaVec, tidak ada daftar elemen yang disimpan; elemen akan dihitung saat diminta. - Baris 5-9 (
impl Counter { fn new() ... }): constructornew()menyiapkan counter di posisi nol. Kebiasaan menyediakan constructor membuat pembuatan iterator ringkas dibanding menulisCounter { count: 0 }di setiap tempat. - Baris 11 (
impl Iterator for Counter {): sintaks implementasi trait persis seperti yang kita latih di section Traits —impl NamaTrait for NamaTipe. - Baris 12 (
type Item = u32;): associated typeItemkita isiu32— pernyataan resmi bahwaCountermenghasilkan angka. Kompilator mulai dari sini mengerti seluruh method bawaanIteratoryang bergantung padaItem. - Baris 14-21 (
fn next(&mut self) -> Option<u32>): satu-satunya method wajib. Logikanya: selamacountmasih di bawah 5, naikkan satu lalu kembalikanSome(self.count); begitu batas tercapai, kembalikanNoneselamanya. Perhatikan dua keputusan penting di dalamnya —self.countdinaikkan sebelum dikembalikan sehingga nilai pertama adalah1bukan0, dan fieldcountdiubah lewat&mut selfpersis seperti yang dituntut tanda tangannext. - Baris 24-29 (
let mut counter = Counter::new(); ...): pengujian manual dengan tiga panggilannextmembuktikan perilakunya:Some(1),Some(2),Some(3)— persis pola dari chapter pertama section ini, kini dari tipe buatan kita sendiri.
Counter dalam Perulangan for #
Pengujian berikutnya membuktikan klaim pertama dari gratisnya trait ini — for langsung menerima Counter (definisi struct dan kedua blok impl di atas tetap dipakai; salin bersama snippet berikut untuk menjalankannya):
fn main() {
for step in Counter::new() {
println!("Langkah ke-{}", step);
}
}Output yang Dihasilkan:
Langkah ke-1
Langkah ke-2
Langkah ke-3
Langkah ke-4
Langkah ke-5Uraian baris-per-baris:
- Baris 2 (
for step in Counter::new()): perulanganformenuntut sumbernya mengimplementasikan traitIntoIterator— dan pustaka standar memberlakukan aturan bahwa setiap tipeIteratorotomatis memenuhi syarat tersebut. Konsekuensinya,Countermasuk kefortanpa kita menulisIntoIteratorsedikit pun. - Baris 3 (
println!("Langkah ke-{}", step);): badan perulangan berjalan lima kali. Pada panggilannextkeenam,CountermengembalikanNonedanforberhenti — mekanisme driver yang sudah kita bedah di chapter pertama, kini menghidupkan tipe buatan sendiri.
Counter di Pipeline Lengkap #
Ujian sesungguhnya adalah pipeline bertingkat. Kita rangkai dua Counter dengan zip, kalikan setiap pasangan lewat map, saring hasil yang habis dibagi tiga lewat filter, lalu jumlahkan dengan sum:
fn main() {
let sum: u32 = Counter::new()
.zip(Counter::new().skip(1))
.map(|(a, b)| a * b)
.filter(|x| x % 3 == 0)
.sum();
println!("Jumlah hasil kali yang habis dibagi 3: {}", sum);
}Output yang Dihasilkan:
Jumlah hasil kali yang habis dibagi 3: 18Mari kita lacak nilai elemen tahap demi tahap, karena di sinilah pembelajaran sebenarnya terjadi:
| Tahap pipeline | Nilai yang mengalir |
|---|---|
Counter::new() | 1, 2, 3, 4, 5 |
zip(Counter::new().skip(1)) | (1, 2), (2, 3), (3, 4), (4, 5) |
map (perkalian tiap pasangan) | 2, 6, 12, 20 |
filter (habis dibagi 3) | 6, 12 |
sum | 18 |
Uraian baris-per-baris mengikuti tabel di atas:
- Baris 3 (
.zip(Counter::new().skip(1))):Counterpertama menghasilkan 1 sampai 5.Counterkedua juga mulai dari 1, tetapiskip(1)membuang nilai pertamanya sehingga alirannya 2 sampai 5 — penempatanskipsetelahCounter::new()mengubah titik mulai pasangan.ziplalu menjodohkan keduanya berdampingan dan berhenti ketika sisi yang lebih pendek habis: empat pasangan. - Baris 4 (
.map(|(a, b)| a * b)): closure menerima tuple hasilzipyang langsung dipecah pola(a, b). Perkalian menghasilkan 2, 6, 12, 20 — perhatikan bahwa elemen di sini sudah bukan keluaranCountermentah; tipe mengalir dan berubah melewati adapter, danItem = u32yang kita tetapkan tetap menjadi titik awal yang sah. - Baris 5 (
.filter(|x| x % 3 == 0)): predicate membuang 2 dan 20, menyisakan 6 dan 12. Kedua angka ini lolos karena habis dibagi tiga. - Baris 6 (
.sum()): consumer penutup menjumlahkan seluruh aliran menjadi18— satu angka yang dihasilkan dari duaCounter, empat adapter, dan nol koleksi perantara.
Menghasilkan Koleksi dari Counter #
Penutup bukti: collect juga bekerja normal, sehingga Counter dapat diubah menjadi koleksi ketika hasil hitungannya benar-benar diperlukan sebagai data yang disimpan:
fn main() {
let steps: Vec<u32> = Counter::new().map(|value| value * 10).collect();
println!("Nilai bertingkat: {:?}", steps);
}Output yang Dihasilkan:
Nilai bertingkat: [10, 20, 30, 40, 50]Uraian baris-per-baris:
- Baris 2 (
Counter::new().map(|value| value * 10).collect()): aliran 1 sampai 5 dikalikan sepuluh per elemen, lalucollectmenumpuk hasilnya menjadiVec<u32>— anotasi: Vec<u32>memberi arah tujuan persis seperti chapter Consumer. Lima nilai poin keanggotaan kini menjadi data koleksi yang bisa dipass ke fungsi lain. - Baris 4 (
println!(...)): kelima nilai tampil sebagaiVecbiasa — tidak ada yang membedakan asal-usulnya dari iterator buatan sendiri.
Ringkasan #
Mengimplementasikan trait Iterator untuk tipe buatan hanya menuntut dua hal: menetapkan type Item dan menulis next(&mut self) -> Option<Self::Item> lengkap dengan logika kapan mengembalikan Some dan kapan None. Counter klasik membuktikan seluruh janji kontrak ini: langsung diterima for lewat jembatan IntoIterator, menjalani pipeline zip, skip, map, filter hingga sum menghasilkan 18 tanpa kode tambahan, dan berubah menjadi Vec lewat collect. Dengan ini siklus section Iterator tertutup penuh: kita telah memahami kontraknya, tiga wajah pinjamannya, sifat lazynya, para adapter dan consumer-nya, hingga kemampuan membangunnya sendiri — bekal yang membuat pemrosesan data di Rust terasa deklaratif, aman, dan hemat sekaligus.