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:

rust
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:

text
Harga berpajak: [16500, 19250, 22000]
Harga asli tetap ada: [15000, 17500, 20000]

Mari kita bedah baris demi baris:

  1. Baris 4 (let with_tax: Vec<i32> = ...collect();): anotasi : Vec<i32> memberitahu collect koleksi apa yang harus dibangun. Method collect sendiri tidak tahu — dan tidak mau menebak — apakah hasilnya Vec, HashMap, String, atau tipe koleksi lainnya.
  2. Baris 4 (bagian prices.iter().map(...)): pipeline tetap lazy sampai collect dipanggil. Saat itulah collect menarik seluruh elemen satu per satu, menjalankan closure map pada masing-masing, dan menumpuk hasilnya ke dalam Vec yang sedang dibangun.
  3. Baris 7 (println!(..., prices)): prices masih hidup dan utuh karena pipeline berdiri di atas pinjaman iter(). with_tax adalah koleksi baru yang sama sekali terpisah — dua Vec hidup 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:

rust
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:

text
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:

rust
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:

text
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:

rust
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:

text
Kode produk: KOPIGAYO

Uraian baris-per-baris:

  1. Baris 4 (let label: String = code): anotasi : String menunjuk FromIterator<char> sebagai target pembangunan.
  2. Baris 5 (.chars()): iterator karakter dari &str — elemennya bertipe char, satu per satu dari “kopi gayo”.
  3. Baris 6 (.filter(|c| *c != ' ')): predicate membuang karakter spasi; delapan karakter lolos.
  4. Baris 7 (.map(|c| c.to_ascii_uppercase())): setiap karakter diubah ke huruf kapital.
  5. Baris 8 (.collect()): seluruh hasil karakter dirangkai menjadi satu String utuh “KOPIGAYO” — tanpa satu pun String sementara per tahap.

Bentuk ketiga adalah koleksi pasangan kunci-nilai. HashMap mengimplementasikan FromIterator<(K, V)>, sehingga iterator yang menghasilkan tuple akan dikumpulkan langsung menjadi pemetaan:

rust
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:

text
Harga Teh Melati: Rp8000
Harga Gula Aren: Rp5000

Mari kita bedah bagian pentingnya:

  1. Baris 4-8 (let menu = vec![...]): sumber berupa Vec berisi tuple (&str, i32) — bentuk data yang persis dibutuhkan HashMap.
  2. Baris 10 (let price_list: HashMap<&str, i32> = menu.into_iter().collect();): into_iter() menyerahkan tuple satu per satu, dan collect membangun HashMap dengan elemen pertama tuple sebagai key serta elemen kedua sebagai value. Kita memakai into_iter() karena menu memang dimaksudkan berubah bentuk menjadi pemetaan, bukan dipakai lagi sebagai Vec.
  3. Baris 12-13 (price_list["Teh Melati"]): hasil collect adalah HashMap sungguhan 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:

rust
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:

text
Total belanja: Rp52500
Jumlah item: 3
Harga termahal: Some(20000)

Uraian baris-per-baris:

  1. Baris 4 (let total: i32 = prices.iter().sum();): sum menarik seluruh elemen dan menjumlahkannya mulai dari nol — di sini &i32 khas iter() tetap sah karena Rust menyediakan implementasi penjumlahan untuk iterator berisi reference angka.
  2. Baris 5 (let jumlah_item = prices.iter().count();): count menghitung banyaknya elemen tanpa membutuhkan anotasi — tipe kembaliannya pasti usize.
  3. Baris 6 (let termahal = prices.iter().max();): max mengembalikan Option<&i32>, bukan &i32 — koleksi kosong tidak punya nilai terbesar, sehingga Option adalah jawaban jujurnya. Output Some(20000) mencerminkan bentuk tersebut, dan pola match dari 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:

rust
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:

text
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:

rust
fn main() {
    let labels = vec![String::from("Kopi Gayo"), String::from("Teh Melati")];

    println!("Gabungan: {}", labels.join(", "));
}

Output yang Dihasilkan:

text
Gabungan: Kopi Gayo, Teh Melati

Ringkasan #

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.