Sejauh ini seluruh pipeline kita berakhir di perulangan for: elemen ditarik, dicetak, lalu hilang. Padahal hasil pengolahan data biasanya justru dibutuhkan sebagai data — daftar harga baru yang akan dikirim ke fungsi lain, teks label yang disimpan ke berkas, pemetaan nama ke harga untuk dicari berkali-kali, atau sekadar satu angka total. Peran tersebut diisi oleh consumer: method yang mengonsumsi iterator sampai habis dan mengembalikan sesuatu. Dua consumer terpenting sehari-hari adalah collect, yang membangun koleksi dari elemen-elemen, dan sum, yang menjumlahkannya menjadi satu nilai. Chapter ini membedah keduanya beserta count dan max, termasuk dua pesan galat kompilator yang hampir pasti kita temui: tipe yang tidak bisa disimpulkan pada collect, dan String yang menolak di-sum.
collect ke Vec #
Kita mulai dari kebutuhan klasik: pipeline harga berpajak dari chapter sebelumnya, kini hasilnya harus menjadi Vec baru yang bisa dipakai di luar perulangan:
fn main() {
let prices = vec![15000, 17500, 20000];
let with_tax: Vec<i32> = prices.iter().map(|price| price + (price / 10)).collect();
println!("Harga berpajak: {:?}", with_tax);
println!("Harga asli tetap ada: {:?}", prices);
}Output yang Dihasilkan:
Harga berpajak: [16500, 19250, 22000]
Harga asli tetap ada: [15000, 17500, 20000]Mari kita bedah baris demi baris:
- Baris 4 (
let with_tax: Vec<i32> = ...collect();): anotasi: Vec<i32>memberitahucollectkoleksi apa yang harus dibangun. Methodcollectsendiri tidak tahu — dan tidak mau menebak — apakah hasilnyaVec,HashMap,String, atau tipe koleksi lainnya. - Baris 4 (bagian
prices.iter().map(...)): pipeline tetap lazy sampaicollectdipanggil. Saat itulahcollectmenarik seluruh elemen satu per satu, menjalankan closuremappada masing-masing, dan menumpuk hasilnya ke dalamVecyang sedang dibangun. - Baris 7 (
println!(..., prices)):pricesmasih hidup dan utuh karena pipeline berdiri di atas pinjamaniter().with_taxadalah koleksi baru yang sama sekali terpisah — duaVechidup berdampingan tanpa saling merebut kepemilikan.
Bentuk lain yang sering kita jumpai di kode Rust sungguhan memakai sintaks turbofish — tanda ::<...> yang menempel langsung pada nama method:
fn main() {
let prices = vec![15000, 17500, 20000];
let with_tax = prices.iter().map(|price| price + (price / 10)).collect::<Vec<_>>();
println!("Harga berpajak: {:?}", with_tax);
}Output yang Dihasilkan:
Harga berpajak: [16500, 19250, 22000]Pada baris 4, collect::<Vec<_>>() menyatakan “bangunlah Vec” sambil menyerahkan tipe elemennya kepada kompiler lewat garis bawah _ — kompiler menyimpulkan i32 dari hasil closure map. Kedua bentuk setara: anotasi : Vec<i32> di variabel atau turbofish ::<Vec<_>>() di pemanggilan. Kapan pakai yang mana? Turbofish menang saat hasilnya langsung dipakai dalam ekspresi panjang; anotasi tipe di variabel menang saat kita ingin tipe variabel terbaca jelas di deklarasinya.
Kapan Tipe Harus Dituliskan #
Sekarang kita buktikan bahwa collect memang tidak mau menebak. Berikut kode yang sama persis tanpa anotasi tipe apa pun:
fn main() {
let prices = vec![15000, 17500, 20000];
let doubled = prices.iter().map(|price| price * 2).collect();
println!("Harga baru: {:?}", doubled);
}Kompilator menolak dengan pesan galat asli berikut:
error[E0283]: type annotations needed
--> main.rs:4:9
|
4 | let doubled = prices.iter().map(|price| price * 2).collect();
| ^^^^^^^ ------- type must be known at this point
|
= note: the type must implement `FromIterator<i32>`
note: required by a bound in `collect`
--> /rustc/b940084d7eb6a299eb4bfeb8e34901bc051e7ac4/library/core/src/iter/traits/iterator.rs:2073:4
help: consider giving `doubled` an explicit type
|
4 | let doubled: Vec<_> = prices.iter().map(|price| price * 2).collect();
| ++++++++
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0283`.Pesan E0283 di atas — pada beberapa versi rustc lain ditandai E0282, satu keluarga galat berjudul sama type annotations needed — menyatakan inti masalahnya: kompiler hanya tahu bahwa hasil harus mengimplementasikan FromIterator<i32>, dan banyak tipe memenuhi syarat itu. Vec<i32>, VecDeque<i32>, HashSet<i32> semuanya kandidat sah; tanpa satu petunjuk tambahan, kompiler menolak memilih secara membabi buta. Perbaikannya persis seperti saran kompilator: tambahkan : Vec<_> pada variabel, atau turbofish ::<Vec<_>>() pada pemanggilan — keduanya memberi petunjuk minimal yang dibutuhkan.
collect ke String dan HashMap #
Kekuatan collect bukan hanya soal Vec: tipe apa pun yang mengimplementasikan trait FromIterator bisa menjadi tujuan. String, misalnya, mengimplementasikan FromIterator<char> — kita bisa membangun teks dari iterator karakter:
fn main() {
let code = "kopi gayo";
let label: String = code
.chars()
.filter(|c| *c != ' ')
.map(|c| c.to_ascii_uppercase())
.collect();
println!("Kode produk: {}", label);
}Output yang Dihasilkan:
Kode produk: KOPIGAYOUraian baris-per-baris:
- Baris 4 (
let label: String = code): anotasi: StringmenunjukFromIterator<char>sebagai target pembangunan. - Baris 5 (
.chars()): iterator karakter dari&str— elemennya bertipechar, satu per satu dari “kopi gayo”. - Baris 6 (
.filter(|c| *c != ' ')): predicate membuang karakter spasi; delapan karakter lolos. - Baris 7 (
.map(|c| c.to_ascii_uppercase())): setiap karakter diubah ke huruf kapital. - Baris 8 (
.collect()): seluruh hasil karakter dirangkai menjadi satuStringutuh “KOPIGAYO” — tanpa satu punStringsementara per tahap.
Bentuk ketiga adalah koleksi pasangan kunci-nilai. HashMap mengimplementasikan FromIterator<(K, V)>, sehingga iterator yang menghasilkan tuple akan dikumpulkan langsung menjadi pemetaan:
use std::collections::HashMap;
fn main() {
let menu = vec![
("Kopi Gayo", 15000),
("Teh Melati", 8000),
("Gula Aren", 5000),
];
let price_list: HashMap<&str, i32> = menu.into_iter().collect();
println!("Harga Teh Melati: Rp{}", price_list["Teh Melati"]);
println!("Harga Gula Aren: Rp{}", price_list["Gula Aren"]);
}Output yang Dihasilkan:
Harga Teh Melati: Rp8000
Harga Gula Aren: Rp5000Mari kita bedah bagian pentingnya:
- Baris 4-8 (
let menu = vec![...]): sumber berupaVecberisi tuple(&str, i32)— bentuk data yang persis dibutuhkanHashMap. - Baris 10 (
let price_list: HashMap<&str, i32> = menu.into_iter().collect();):into_iter()menyerahkan tuple satu per satu, dancollectmembangunHashMapdengan elemen pertama tuple sebagai key serta elemen kedua sebagai value. Kita memakaiinto_iter()karenamenumemang dimaksudkan berubah bentuk menjadi pemetaan, bukan dipakai lagi sebagaiVec. - Baris 12-13 (
price_list["Teh Melati"]): hasilcollectadalahHashMapsungguhan yang mendukung pengindeksan — bukti bahwa pipeline telah menjadi struktur data permanen yang siap dipakai di bagian program mana pun.
sum, count, dan max #
Ketika hasil yang dibutuhkan bukan koleksi melainkan satu angka, keluarga consumer agregat mengambil peran:
fn main() {
let prices = vec![15000, 17500, 20000];
let total: i32 = prices.iter().sum();
let jumlah_item = prices.iter().count();
let termahal = prices.iter().max();
println!("Total belanja: Rp{}", total);
println!("Jumlah item: {}", jumlah_item);
println!("Harga termahal: {:?}", termahal);
}Output yang Dihasilkan:
Total belanja: Rp52500
Jumlah item: 3
Harga termahal: Some(20000)Uraian baris-per-baris:
- Baris 4 (
let total: i32 = prices.iter().sum();):summenarik seluruh elemen dan menjumlahkannya mulai dari nol — di sini&i32khasiter()tetap sah karena Rust menyediakan implementasi penjumlahan untuk iterator berisi reference angka. - Baris 5 (
let jumlah_item = prices.iter().count();):countmenghitung banyaknya elemen tanpa membutuhkan anotasi — tipe kembaliannya pastiusize. - Baris 6 (
let termahal = prices.iter().max();):maxmengembalikanOption<&i32>, bukan&i32— koleksi kosong tidak punya nilai terbesar, sehinggaOptionadalah jawaban jujurnya. OutputSome(20000)mencerminkan bentuk tersebut, dan polamatchdari section Pattern Matching siap membedahnya.
String Tidak Bisa di-sum #
Penutup chapter ini adalah galat yang wajar muncul ketika intuisi “gabungkan teks” bertemu dengan kata sum. Kita coba menjumlahkan iterator yang elemennya String:
fn main() {
let labels = vec![String::from("Kopi Gayo"), String::from("Teh Melati")];
let combined: String = labels.into_iter().sum();
println!("Gabungan: {}", combined);
}Kompilator menolak dengan pesan galat asli berikut:
error[E0277]: a value of type `String` cannot be made by summing an iterator over elements of type `String`
--> main.rs:4:47
|
4 | let combined: String = labels.into_iter().sum();
| ^^^ value of type `String` cannot be made by summing a `std::iter::Iterator<Item=String>`
|
= help: the trait `Sum` is not implemented for `String`
= help: the following other types implement trait `Sum<A>`:
`Duration` implements `Sum<&'a Duration>`
`Duration` implements `Sum`
`Option<T>` implements `Sum<Option<U>>`
`Result<T, E>` implements `Sum<Result<U, E>>`
`Saturating<u128>` implements `Sum<&'a Saturating<u128>>`
`Saturating<u128>` implements `Sum`
`Saturating<u16>` implements `Sum<&'a Saturating<u16>>`
`Saturating<u16>` implements `Sum`
and 88 others
note: the method call chain might not have had the expected associated types
--> main.rs:4:35
|
2 | let labels = vec![String::from("Kopi Gayo"), String::from("Teh Melati")];
| ----------------------------------------------------------- this expression has type `Vec<String>`
3 |
4 | let combined: String = labels.into_iter().sum();
| ^^^^^^^^^^^ `Iterator::Item` is `String` here
note: required by a bound in `std::iter::Iterator::sum`
--> /rustc/b940084d7eb6a299eb4bfeb8e34901bc051e7ac4/library/core/src/iter/traits/iterator.rs:3665:4
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0277`.Pesan E0277 ini menjelaskan akar masalahnya: sum bekerja dengan cara menumpuk nilai bertahap menggunakan operasi penjumlahan, dan String tidak mengimplementasikan kemampuan yang dibutuhkan — trait Sum tidak berlaku untuk String karena String memang tidak punya operator + untuk String + String (penjumlahan teks di Rust hanya mengenal String + &str). Penggabungan teks bukan wilayah sum. Untuk kebutuhan tersebut, collect ke String seperti contoh kode produk di atas, atau method join pada slice, adalah jalannya:
fn main() {
let labels = vec![String::from("Kopi Gayo"), String::from("Teh Melati")];
println!("Gabungan: {}", labels.join(", "));
}Output yang Dihasilkan:
Gabungan: Kopi Gayo, Teh MelatiRingkasan #
Consumer menutup siklus hidup iterator: collect menarik seluruh elemen dan membangun koleksi tujuan apa pun yang mengimplementasikan FromIterator — Vec<i32>, String dari karakter, HashMap dari tuple — dengan syarat tipe tujuan dinyatakan lewat anotasi atau turbofish, karena tanpa petunjuk kompilator menolak memilih di antara banyak kandidat (E0283, kerabat E0282). Keluarga agregat sum, count, dan max meringkas iterator menjadi satu nilai, dengan max yang jujur mengembalikan Option. Usaha menjumlahkan String ditolak sebagai E0277 karena teks tidak memiliki operasi penjumlahan; penggabungan adalah ranah collect atau join. Dengan adapter dan consumer lengkap, kita tinggal memperkaya kotak peralatan dengan adapter khusus penyanding dua iterator — pembahasan chapter berikutnya.