collect dan Turbofish

Membangun collection dari iterator serta sintaks ::<>.

Daftar isi

Topik ini membahas collect, yaitu consumer yang mengubah seluruh aliran iterator menjadi satu koleksi atau satu nilai terkumpul. Pembahasan mencakup kerja samanya dengan trait FromIterator, beragam bentuk koleksi yang dapat dibangun dari iterator yang sama, kegagalan inferensi tipe ketika bentuk target tidak disebutkan, serta sintaks turbofish ::<> untuk menuliskan parameter tipe langsung pada pemanggilan metode. Setelah membaca topik ini, pipeline iterator dapat ditutup dengan koleksi yang bentuknya dipilih secara sadar, dan error inferensi tipe pada collect dapat diselesaikan lewat dua pintu, yaitu anotasi variabel dan turbofish.

collect sebagai pintu keluar pipeline#

Metode collect menghabiskan iterator dan membangun satu nilai dari seluruh elemennya. Bentuk nilai itu ditentukan oleh tipe tujuan lewat trait FromIterator, sehingga iterator yang sama dapat dikumpulkan menjadi Vec, String, peta, atau himpunan bergantung tipe yang diminta. Pemilihan tipe terjadi pada tanda tangan from_iter, bukan pada aliran nilainya.

rust Fragmen: inti trait FromIterator
// tidak-dijalankan
trait FromIterator<A> {
    fn from_iter<T: IntoIterator<Item = A>>(iter: T) -> Self;
}

Parameter iter hanya mensyaratkan IntoIterator, dan itulah sebabnya from_iter dapat menerima iterator maupun koleksi apa pun yang dapat diubah menjadi iterator. Program berikut mengumpulkan satu sumber data yang sama menjadi tiga bentuk berbeda, lalu membandingkannya dengan sum yang meringkas menjadi satu bilangan.

rust beberapa-target.rs
use std::collections::BTreeMap;

fn main() {
    let nilai = [("Ali", 80), ("Budi", 95), ("Citra", 60)];

    let nama: Vec<String> = nilai.iter().map(|(n, _)| n.to_uppercase()).collect();
    println!("Vec     : {:?}", nama);

    let rapor: BTreeMap<&str, i32> = nilai.iter().copied().collect();
    println!("BTreeMap: {:?}", rapor);

    let singkat: String = "kompilator".chars().take(4).collect();
    println!("String  : {}", singkat);

    let total: i32 = nilai.iter().map(|(_, s)| s).sum();
    println!("sum     : {}", total);
}
text Keluaran
Vec     : ["ALI", "BUDI", "CITRA"]
BTreeMap: {"Ali": 80, "Budi": 95, "Citra": 60}
String  : komp
sum     : 235

Sumber data yang sama mengalir ke Vec<String> pada baris pertama dan ke BTreeMap<&str, i32> pada baris kedua, dan bentuk keluarannya mengikuti tipe tujuan tersebut. Peta terbentuk karena FromIterator untuk peta memasangkan komponen pertama sebagai kunci dan komponen kedua sebagai nilai. Baris ketiga memperlihatkan kumpulan karakter menjadi String tanpa perulangan push manual, sedangkan sum menutup perbandingan sebagai consumer yang tidak membangun koleksi sama sekali. Ketiga pemakaian collect ditopang anotasi tipe pada variabelnya, dan anotasi itulah pintu pertama untuk menetapkan bentuk hasil.

Ketika tipe target tidak dapat disimpulkan#

Kompilator menebak tipe tujuan collect dari pemakaiannya, misalnya anotasi pada variabel, tipe parameter fungsi yang menerima hasilnya, atau pemakaian metode yang hanya dimiliki satu tipe. Ketika tidak ada satu pun petunjuk, kompilator berhenti dengan error inferensi karena banyak tipe yang memenuhi syarat FromIterator. Program berikut menghapus seluruh petunjuk tersebut dengan menampung hasil collect pada variabel polos tanpa anotasi.

bash
export PATH="$HOME/.cargo/bin:$PATH"
# buat program yang mengumpulkan rentang tanpa petunjuk tipe tujuan
printf 'fn main() {\n    let angka = (1..=5).collect();\n    println!("{:?}", angka);\n}\n' > rancu.rs
# kompilasi gagal; catatan pustaka internal disaring agar keluaran ringkas
rustc --edition=2021 rancu.rs -o rancu 2>&1 | sed -E '/^ *= note|^note: required by a bound|^ *--> \/rustc\//d' | head -14
text Keluaran
error[E0283]: type annotations needed
 --> rancu.rs:2:9
  |
2 |     let angka = (1..=5).collect();
  |         ^^^^^           ------- type must be known at this point
  |
help: consider giving `angka` an explicit type
  |
2 |     let angka: Vec<_> = (1..=5).collect();
  |              ++++++++

error: aborting due to 1 previous error

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

Error E0283 muncul pada variabel angka karena Vec, HashSet, dan banyak tipe lain sama-sama mampu menerima i32, sehingga kompilator tidak memiliki dasar untuk memilih. Saran pada bagian help memperlihatkan perbaikannya secara langsung, yaitu memberi anotasi tipe pada variabel, dan Vec<_> sudah cukup karena tipe elemen i32 dapat disimpulkan dari rentangnya. Pemotongan keluaran dengan sed hanya membuang catatan lokasi internal pustaka standar yang bergantung pada versi kompilator, sedangkan inti error dan sarannya tetap utuh. Program yang sama dapat diperbaiki lewat pintu kedua, yaitu turbofish pada bagian berikutnya.

Turbofish ::<>#

Turbofish adalah bentuk tulis ::<Tipe> yang diletakkan di antara nama metode dan tanda kurung argumennya, misalnya collect::<Vec<i32>>(). Nama itu berasal dari kemiripan simbol ::<> dengan seekor ikan, dan sintaks ini berlaku untuk semua metode generik, bukan hanya collect. Kegunaannya adalah menetapkan parameter tipe pada titik pemanggilan, yang berguna ketika tidak ada variabel yang dapat dianotasi, misalnya pada pemanggilan di tengah ekspresi.

rust turbofish.rs
fn main() {
    let teks = ["10", "25", "7"];

    let angka = teks
        .iter()
        .map(|t| t.parse::<i32>().expect("bilangan valid"))
        .collect::<Vec<i32>>();
    println!("turbofish: {:?}", angka);

    let lebar = "1280".parse::<u16>().unwrap();
    let tinggi = "720".parse::<u16>().unwrap();
    println!("resolusi : {}x{}", lebar, tinggi);

    let total: i32 = angka.iter().sum();
    let total_lagi = angka.iter().sum::<i32>();
    println!("total    : {} dan {}", total, total_lagi);
}
text Keluaran
turbofish: [10, 25, 7]
resolusi : 1280x720
total    : 42 dan 42

Turbofish parse::<i32>() di dalam closure map menetapkan hasil penguraian sebelum elemen mana pun diolah, dan turbofish collect::<Vec<i32>>() menetapkan wadah hasil di ujung rantai. Apabila kedua turbofish dihapus, error E0284 muncul pada parse karena teks dapat diurai menjadi banyak tipe bilangan, sedangkan apabila hanya turbofish pada collect yang dihapus, error E0283 muncul kembali pada collect. Turbofish parse::<u16>() menetapkan u16 sebagai tipe tujuan penguraian teks resolusi. Pasangan total memperlihatkan dua pintu yang setara, yaitu anotasi : i32 pada variabel dan sum::<i32>() pada pemanggilan, yang menghasilkan nilai sama. Pilih anotasi variabel saat hasilnya langsung disimpan, dan pilih turbofish saat pemanggilan berada di tengah ekspresi atau tipenya hanya relevan pada satu titik.

Kesalahan umum#

Menaruh turbofish setelah tanda kurung. Bentuk collect()::<Vec<i32>>() tidak lolos parsing karena parameter tipe hanya boleh menempel pada nama sebelum argumen. Tulis collect::<Vec<i32>>>(), atau berpindah ke anotasi variabel bila posisi tersebut lebih terbaca.

Memakai iterator yang sudah dikumpulkan. collect memindahkan iterator ke dalam from_iter, sehingga iterator yang sudah dikumpulkan tidak dapat dipanggil atau dikumpulkan lagi, dan pemakaian berikutnya ditolak sebagai nilai yang telah dipindahkan. Simpan hasil collect di variabel lalu iterasi hasilnya, atau buat iterator baru dari sumber data mula-mula.

Mengira Vec<_> selalu menyelesaikan inferensi. Garis bawah pada Vec<_> hanya menunda keputusan ke tipe elemen, sehingga apabila tipe elemennya sendiri tidak dapat disimpulkan, error inferensi tetap muncul. Tambahkan petunjuk pada titik yang dilaporkan error, biasanya pada closure pengubah atau pada pemanggilan parse di dalam rantai.

Ringkasan#

  • collect menghabiskan iterator dan membangun satu nilai lewat FromIterator, dengan bentuk hasil yang ditentukan tipe tujuan, bukan oleh aliran nilainya.
  • Satu iterator dapat dikumpulkan menjadi Vec, String, BTreeMap, atau tipe lain yang mengimplementasikan FromIterator untuk elemen tersebut.
  • Tanpa petunjuk tipe, collect gagal dikompilasi dengan error E0283, dan kompilator biasanya menyertakan saran anotasi secara langsung.
  • Turbofish ::<Tipe> menetapkan parameter tipe pada titik pemanggilan dan berlaku untuk semua metode generik, dengan posisi yang menempel pada nama metode sebelum kurung.

Referensi#