Lewati ke konten

Error Rust E0283: type annotations needed (sum, collect, dan into)

E0283 artinya Rust tahu apa yang harus bisa dilakukan nilaimu, tapi banyak tipe yang bisa melakukannya, dan Rust tidak mau memilihkan salah satunya untukmu. Biasanya kamu bertemu error ini tepat setelah .sum(), .collect(), atau .into(). Ini salah satu error yang paling gampang diperbaiki: sebutkan tipe yang kamu mau, selesai. Untuk error lain di seri ini, lihat Penjelasan error compiler Rust.

error[E0283]: type annotations needed
--> src/main.rs:3:9
|
3 | let total = prices.iter().sum();
| ^^^^^ --- type must be known at this point
|
= note: the type must implement `Sum<&i32>`
= help: the following 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: required by a bound in `std::iter::Iterator::sum`
help: consider giving `total` an explicit type
|
3 | let total: /* Type */ = prices.iter().sum();
| ++++++++++++

Compiler bilang ia butuh anotasi tipe (type annotations needed): tipe total harus diketahui di titik ini, dan ada banyak tipe yang bisa menjadi hasil penjumlahan. Ia menyarankan untuk memberi total tipe secara eksplisit.

Ini program yang menghasilkan error tersebut:

fn main() {
let prices = vec![250, 120, 99]; // harga dalam sen
let total = prices.iter().sum();
println!("Total: {total} cents");
}

.collect() dan .into() memberi error yang sama dengan note yang berbeda. Untuk let words = text.split(' ').collect();, note-nya the type must implement `FromIterator<&str>`. Untuk let name = "Ferris".into();, note-nya the type must implement `From<&str>`.

Baca pesannya dari atas ke bawah:

  1. ^^^^^ di bawah total: inilah variabel yang tipenya tidak bisa diputuskan Rust.
  2. --- di bawah sum: “type must be known at this point” (tipenya harus diketahui di titik ini). sum bisa mengembalikan banyak tipe yang berbeda, jadi Rust butuh jawabannya di sini.
  3. Note-nya: the type must implement `Sum<&i32>`. Inilah aturan yang ditemukan Rust: “total harus bertipe sesuatu yang bisa dibuat dengan menjumlahkan nilai-nilai &i32”.
  4. Daftar help: tipe-tipe yang tahu cara menjadi “hasil penjumlahan sesuatu”. Jumlahnya banyak: setiap tipe angka, Duration, bahkan Option dan Result. Rust menunjukkan delapan dan bilang “and 88 others” (dan 88 lainnya). Itulah yang dimaksud orang dengan multiple impls satisfy this bound: lebih dari satu tipe punya impl yang memenuhi aturan itu.

Bayangkan kamu memesan “sesuatu untuk diminum” di kafe. Itu aturan yang jelas (harus bisa diminum), tapi teh, jus, dan air putih semuanya memenuhi syarat. Pelayannya tidak bisa membawakan apa-apa sampai kamu memilih. E0283 adalah Rust yang sedang memegang nampan dan menunggu jawabanmu.

sum, collect, dan into bersifat generik pada tipe kembaliannya. Tipe yang kamu minta menentukan apa yang dilakukan fungsi itu. Trait-nya (Sum, FromIterator, From) hanyalah aturannya. Jadi kalau tidak ada yang memberi tahu Rust tipenya, Rust punya aturan tapi tidak punya satu jawaban pasti.

Dan jawabannya penting. Mengumpulkan kata-kata yang sama ke dalam Vec<&str> tetap menyimpan duplikatnya, tapi mengumpulkannya ke dalam HashSet<&str> membuang duplikat itu. Menjumlahkan ke dalam u8 akan overflow di 255, sedangkan u64 tidak. Kalau Rust memilihkan tipe untukmu, perubahan kecil di tempat lain bisa diam-diam mengubah perilaku programmu. Rust lebih suka bertanya sekali.

Cara Rust menyimpulkan tipe sendiri (type inference, inferensi tipe) dijelaskan di halaman E0282, jadi tidak diulang di sini. Untuk trait di balik method-method ini, lihat Trait dan Generics.

E0282, E0283, E0284: satu masalah, tiga kode

Section titled “E0282, E0283, E0284: satu masalah, tiga kode”

Ketiganya bilang “type annotations needed” (butuh anotasi tipe), dan bagimu artinya sama: tulis tipenya. Kodenya hanya memberi tahu apa yang sedang dikerjakan Rust saat ia buntu:

  • E0282: Rust sama sekali tidak punya petunjuk tentang tipenya, atau ia butuh tipenya sebelum sampai ke petunjuk itu (misalnya untuk mencari method seperti .len()).
  • E0283 (halaman ini): Rust punya petunjuk berupa aturan trait (must implement `Sum<&i32>`), tapi banyak tipe yang mengikuti aturan itu.
  • E0284: aturannya menyangkut tipe yang menjadi bagian dari tipe yang belum diketahui, seperti tipe error dari parse (cannot satisfy `<_ as FromStr>::Err == _`). Rust tidak bisa memeriksanya sampai ia tahu tipe utamanya.

Kodenya bahkan bisa berubah selagi kamu mengetik. let words = text.split(' ').collect(); yang diikuti println!("{:?}", words) adalah E0283. Ubah baris berikutnya menjadi words.len(), dan error-nya berubah menjadi E0282, karena sekarang Rust butuh tipenya untuk mencari method itu. Perbaikan di bawah bekerja untuk ketiganya.

Perbaikan 1: Beri anotasi pada variabelnya

Section titled “Perbaikan 1: Beri anotasi pada variabelnya”

Pilih ini di kebanyakan kasus. Tambahkan : Tipe setelah nama variabel. Singkat, dan memberi tahu pembaca berikutnya dengan tepat apa yang sedang kamu buat:

use std::collections::HashSet;
fn main() {
let prices = vec![250, 120, 99];
let total: u32 = prices.iter().sum();
let tags = "rust,web,rust,cli";
let list: Vec<&str> = tags.split(',').collect();
let unique: HashSet<&str> = tags.split(',').collect();
println!("Total: {total} sen");
println!("{:?}", list);
println!("Tag unik: {}", unique.len());
}

Output:

Total: 469 sen
["rust", "web", "rust", "cli"]
Tag unik: 3
▶ Coba sendiri

collect() yang sama, dua hasil berbeda: tipe yang kamu tulis memang benar-benar instruksinya. Kamu bisa menyerahkan bagian dalamnya ke Rust dengan _, seperti let list: Vec<_> = ....

Perbaikan 2: Pakai turbofish pada sum dan collect

Section titled “Perbaikan 2: Pakai turbofish pada sum dan collect”

Pilih ini kalau tidak ada variabel yang bisa diberi anotasi: di dalam println!, di tengah rangkaian iterator, atau sebagai argumen fungsi. Taruh tipenya di pemanggilan dengan ::<Tipe> (disebut “turbofish”):

fn main() {
let prices = vec![250, 120, 99];
println!("Total: {} sen", prices.iter().sum::<u32>());
let line = "tea,cake,soup";
let items = line.split(',').collect::<Vec<_>>();
let initials = line.split(',').map(|s| &s[..1]).collect::<String>();
println!("{:?}", items);
println!("Inisial: {initials}");
}

Output:

Total: 469 sen
["tea", "cake", "soup"]
Inisial: tcs
▶ Coba sendiri

.product::<u64>() bekerja dengan cara yang sama. Perbaikan 1 dan Perbaikan 2 sama-sama benar, jadi pilih yang lebih enak dibaca.

Perbaikan 3: Untuk .into(), sebutkan tipe tujuannya (turbofish tidak bisa dipakai)

Section titled “Perbaikan 3: Untuk .into(), sebutkan tipe tujuannya (turbofish tidak bisa dipakai)”

Pilih ini untuk .into() dan .try_into(). Kamu mungkin tergoda menulis "Ferris".into::<String>(), tapi itu menghasilkan error lain, karena parameter tipenya milik trait Into, bukan milik method-nya:

error[E0107]: method takes 0 generic arguments but 1 generic argument was supplied
--> src/main.rs:2:25
|
2 | let name = "Ferris".into::<String>();
| ^^^^ expected 0 generic arguments

Artinya: method into tidak menerima argumen generik, tapi kamu memberinya satu.

Sebagai gantinya, beri anotasi pada variabelnya, atau panggil From langsung pada tipe tujuannya, yang juga enak dibaca:

fn main() {
let name: String = "Ferris".into();
let city = String::from("Lisbon");
let small: u8 = 200;
let big: u64 = small.into();
let also_big = u64::from(small);
let level: i64 = 42;
let byte: u8 = level.try_into().unwrap();
let too_big = u8::try_from(300_i64);
println!("{name} tinggal di {city}");
println!("{big} {also_big} {byte}");
println!("Apakah 300 muat di u8? {}", too_big.is_ok());
}

Output:

Ferris tinggal di Lisbon
200 200 42
Apakah 300 muat di u8? false
▶ Coba sendiri

String::from(...) dan u64::from(...) menyebutkan tipe tujuannya langsung di awal baris, jadi banyak orang lebih suka cara ini daripada .into() kalau tidak ada petunjuk lain. Lihat Konversi.

Perbaikan 4: Pakai nilainya di tempat yang tipenya sudah pasti

Section titled “Perbaikan 4: Pakai nilainya di tempat yang tipenya sudah pasti”

Pilih ini kalau nilainya memang akan dipakai di tempat yang tipenya sudah diketahui, seperti parameter fungsi atau field struct. Rust membaca seluruh fungsi, jadi pemakaian di baris berikutnya pun sudah cukup:

fn print_receipt(total: u32, items: Vec<&str>) {
println!("{} item, {} sen", items.len(), total);
}
fn main() {
let prices = vec![250, 120, 99];
let total = prices.iter().sum(); // u32, berkat print_receipt
let items = "tea,cake,soup".split(',').collect(); // Vec<&str>, alasan yang sama
print_receipt(total, items);
}

Output:

3 item, 469 sen
▶ Coba sendiri

Tidak semua pemakaian dihitung. if prices.iter().sum() > 400 tetap E0283, karena > bisa membandingkan banyak pasangan tipe. Memberikan nilainya ke parameter generik seperti impl Display juga tidak membantu, karena banyak tipe yang Display. Pemakaiannya harus menyebut satu tipe yang pasti.

.collect() langsung di dalam println!. println!("{:?}", line.split(',').collect()); memberi E0283, dan rustc sendiri menyarankan perbaikannya: .collect::<Vec<_>>().

.try_into().unwrap(). let small = big.try_into().unwrap(); memberi E0283 dengan the type must implement `TryFrom<i64>`. Perbaikannya: let small: u8 = big.try_into().unwrap(); atau u8::try_from(big).unwrap().

Default::default() yang diberikan ke fungsi generik. Dengan fn show(value: impl Debug), pemanggilan show(Default::default()) adalah E0283: Rust tidak tahu default milik tipe siapa yang kamu mau. Perbaikannya: show(Settings::default()) atau show(u32::default()). Di barisnya sendiri, let s = Default::default(); malah memberi E0790; lihat Default.

Fungsi generik buatanmu sendiri. Kalau sebuah parameter tipe hanya muncul di tipe kembalian, seperti fn make<T: Default>() -> T, maka let v = make(); adalah E0283. Perbaikannya: make::<Settings>() atau let v: Settings = make();.

  • E0282: type annotations needed, saat Rust sama sekali tidak punya petunjuk tentang tipenya (atau butuh tipenya untuk memanggil method).
  • E0284: type annotations needed dengan cannot satisfy `<_ as FromStr>::Err == _`, hampir selalu dari .parse().
  • E0790: Default::default() (atau fungsi trait lain) dipanggil tanpa menyebut tipenya.
  • E0277: kamu sudah menyebut tipenya, tapi tipe itu tidak bisa dibuat dengan cara tersebut, seperti let n: Vec<i32> = "1 2".split(' ').collect();.
  • E0107: jumlah argumen generik salah, yang kamu dapat dari .into::<String>().
  • Semua error compiler: daftar lengkap halaman error.

Kuis singkat

1. Apa yang disampaikan note E0283 the type must implement Sum<&i32>?

2. Baris mana yang memperbaiki let name = "Ferris".into();?

3. Kenapa mengumpulkan kata-kata yang sama ke Vec<&str> dan ke HashSet<&str> memberi hasil berbeda?

Skor: 0 / 3