Lewati ke konten

Error Rust E0282: type annotations needed (anotasi tipe diperlukan)

E0282 artinya Rust sudah mencoba mencari tahu sebuah tipe sendiri, tapi tidak menemukan cukup petunjuk. Logikamu tidak salah. Compiler hanya butuh kamu menyebutkan satu tipe, biasanya dengan beberapa karakter tambahan saja. Halaman ini menunjukkan perbaikannya dulu, lalu menjelaskan cara Rust menebak tipe (namanya type inference, inferensi tipe), supaya kamu tahu kapan perlu membantunya. Untuk error lain di seri ini, lihat Penjelasan error compiler Rust.

error[E0282]: type annotations needed for `Vec<_>`
--> src/main.rs:2:9
|
2 | let mut scores = Vec::new(); // sebuah Vec berisi... apa?
| ^^^^^^^^^^ ---------- type must be known at this point
|
help: consider giving `scores` an explicit type, where the type for type parameter `T` is specified
|
2 | let mut scores: Vec<T> = Vec::new(); // sebuah Vec berisi... apa?
| ++++++++

Compiler bilang ia butuh anotasi tipe untuk Vec<_> ini, dan menyarankan kamu memberi scores tipe yang jelas.

Ini program yang menghasilkan error tersebut:

fn main() {
let mut scores = Vec::new(); // sebuah Vec berisi... apa?
println!("Scores recorded: {}", scores.len());
}

Kata-kata “type annotations needed” juga muncul dengan tiga kode lain. Artinya sama, dan perbaikan di halaman ini berlaku untuk semuanya:

  • E0283: .collect() atau .sum() tanpa tipe tujuan. rustc menambahkan catatan seperti the type must implement `FromIterator<&str>` atau `Sum<&i32>`, karena banyak tipe yang bisa jadi hasilnya.
  • E0284: "42".parse().unwrap() tanpa tipe tujuan. Catatannya berbunyi cannot satisfy `<_ as FromStr>::Err == _`, cara yang menyeramkan untuk bilang “di-parse menjadi apa?”.
  • E0790: let settings = Default::default();. Judulnya berbunyi cannot call associated function on trait without specifying the corresponding `impl` type: Rust tidak tahu default milik siapa yang kamu mau.

Kode mana yang kamu dapat bergantung pada baris persisnya, dan bisa berubah kalau kamu menambahkan kode lain. Misalnya, let age = "42".parse().unwrap(); menghasilkan E0284, tapi kalau baris berikutnya memanggil age.pow(2), error-nya menjadi E0282. Tidak usah pusing soal nomornya; lihat variabel yang ditunjuk rustc.

Perhatikan dua tanda di bawah baris 2:

  1. ^^^^^^^^^^ di bawah mut scores: inilah variabel yang tipenya tidak bisa ditebak Rust.
  2. ---------- di bawah Vec::new(): “type must be known at this point” (tipenya harus sudah diketahui di titik ini). Vec::new() membuat daftar kosong, tapi daftar kosong berisi apa? Angka? Nama? Rust perlu tahu supaya bisa menyiapkan tempat penyimpanan yang tepat.
  3. Bagian help menyarankan let mut scores: Vec<T>. Di sini T hanya placeholder. Kamu menggantinya dengan tipe sungguhan, seperti Vec<u32>.

Bayangkan sebuah paket di kantor pos dengan kolom “isi paket” yang dibiarkan kosong. Petugasnya tidak bisa mengirimnya sampai ada yang menulis sesuatu di situ. Kamu bisa menulisnya sendiri, atau memasukkan sesuatu ke dalam paket supaya petugasnya bisa melihat.

Setiap nilai di Rust punya satu tipe yang pasti, yang ditentukan saat kompilasi. Biasanya kamu tidak perlu menuliskannya, karena Rust membaca kodemu dan menyimpulkannya sendiri. let count = 5; adalah i32. let name = String::from("Ferris"); adalah String. Itulah type inference, dan pelajaran Variabel menunjukkan lebih banyak contohnya.

Inference memakai petunjuk dari seluruh fungsi, bukan hanya satu baris. Kalau nanti kamu memanggil scores.push(90), itu petunjuk: Vec-nya berisi angka. Kalau kamu memberikan scores ke fungsi yang menerima &[u32], itu petunjuk yang lebih baik lagi. E0282 muncul kalau tidak ada petunjuk sama sekali, atau kalau Rust butuh tipenya sebelum sampai ke petunjuk itu (misalnya untuk mencari sebuah method).

Beberapa fungsi bersifat generik pada tipe kembaliannya: parse, collect, sum, Default::default, Vec::new, None. Fungsi-fungsi ini bisa menghasilkan banyak tipe berbeda, jadi Rust tidak bisa memilihkan satu untukmu. Rust tidak mau menebak, karena tebakan yang salah akan diam-diam mengubah perilaku programmu. Lihat Generics untuk tahu bagaimana satu fungsi bisa mengembalikan banyak tipe.

Perbaikan 1: Beri anotasi pada variabelnya

Section titled “Perbaikan 1: Beri anotasi pada variabelnya”

Pilih ini di kebanyakan kasus. Tambahkan : Tipe setelah nama variabelnya. Singkat, dan sekaligus menjadi dokumentasi untuk orang berikutnya yang membaca kodemu:

fn main() {
let scores: Vec<u32> = Vec::new();
let age: u32 = "42".parse().unwrap();
let words: Vec<&str> = "merah hijau biru".split(' ').collect();
let winner: Option<String> = None;
println!("Skor tercatat: {}", scores.len());
println!("Tahun depan umurmu {}", age + 1);
println!("{:?}", words);
println!("{:?}", winner);
}

Output:

Skor tercatat: 0
Tahun depan umurmu 43
["merah", "hijau", "biru"]
None
▶ Coba sendiri

Kamu tidak selalu harus menulis tipenya lengkap. let words: Vec<_> = ...collect(); artinya “sebuah Vec, dan isinya silakan kamu tebak sendiri”. Tanda _ meminta Rust mengisi bagian itu. Selengkapnya tentang menulis tipe di Tipe data.

Pilih ini kalau tidak ada variabel yang bisa diberi anotasi, misalnya di tengah rangkaian pemanggilan iterator. Kamu menaruh tipenya langsung di pemanggilan fungsi dengan ::<Tipe>. Orang menyebut ::<> “turbofish” karena bentuknya sedikit mirip ikan:

fn main() {
let input = "4 15 7";
let numbers = input
.split(' ')
.map(|s| s.parse::<u32>().unwrap())
.collect::<Vec<_>>();
let total = numbers.iter().sum::<u32>();
println!("{:?}", numbers);
println!("Total: {total}");
}

Output:

[4, 15, 7]
Total: 26
▶ Coba sendiri

Tiga pemanggilan turbofish yang paling sering adalah parse::<i32>(), collect::<Vec<_>>(), dan sum::<i32>(). Perbaikan 1 dan Perbaikan 2 sama-sama benar; let total: u32 = numbers.iter().sum(); melakukan hal yang sama. Pilih yang menurutmu lebih enak dibaca.

Perbaikan 3: Pakai nilainya, supaya Rust bisa menebak tipenya

Section titled “Perbaikan 3: Pakai nilainya, supaya Rust bisa menebak tipenya”

Pilih ini kalau error-nya muncul karena kodemu belum selesai. Sering kali kamu membuat Vec kosong, dan kode yang mengisinya baru ditulis belakangan. Begitu nilainya dipakai, Rust mendapat petunjuknya dan error-nya hilang, tanpa perlu anotasi:

fn print_team(names: &[String]) {
for name in names {
println!("- {name}");
}
}
fn main() {
let mut team = Vec::new(); // tipenya belum diketahui... untuk sementara
team.push(String::from("Ferris")); // sekarang Rust tahu: Vec<String>
team.push(String::from("Corro"));
print_team(&team);
}

Output:

- Ferris
- Corro
▶ Coba sendiri

Urutan sedikit berpengaruh. Petunjuknya harus muncul sebelum Rust butuh tipenya untuk memanggil sebuah method. Closure seperti let shout = |s| s.to_uppercase(); tetap gagal meskipun kamu memanggil shout("hi") di baris berikutnya, karena Rust harus tahu apa itu s untuk menemukan .to_uppercase(). Di situ, tulis tipenya: |s: &str| s.to_uppercase(). Lihat Closure.

Perbaikan 4: Sebutkan tipe untuk None dan Default::default()

Section titled “Perbaikan 4: Sebutkan tipe untuk None dan Default::default()”

Pilih ini untuk nilai yang sama sekali tidak membawa petunjuk. None tidak bilang apa yang akan ia simpan, dan Default::default() tidak bilang default milik siapa yang kamu mau. Tuliskan tipenya di dalam ekspresi:

#[derive(Debug, Default)]
struct Settings {
volume: u8,
muted: bool,
}
fn main() {
let fastest_lap = None::<f64>; // bukan: None
let settings = Settings::default(); // bukan: Default::default()
let backup: Settings = Default::default(); // anotasi juga bisa
println!("{:?}", fastest_lap);
println!("{:?}", settings);
println!("Volume cadangan: {}", backup.volume);
}

Output:

None
Settings { volume: 0, muted: false }
Volume cadangan: 0
▶ Coba sendiri

Settings::default() adalah pilihan paling jelas: terbaca sebagai “Settings default”. None::<f64> praktis, tapi let fastest_lap: Option<f64> = None; (Perbaikan 1) lebih umum dipakai. Lihat Option.

Mem-parse teks menjadi angka. let guess = line.trim().parse().unwrap(); menghasilkan E0284 (atau E0282 kalau kamu memanggil method pada guess sesudahnya). parse bisa menghasilkan i32, u8, f64, dan banyak lagi. Perbaikannya: let guess: i32 = ... atau .parse::<i32>(). Membandingkan guess dengan variabel i32 belakangan tidak cukup di sini, karena parse butuh tipenya tepat saat dipanggil.

.collect(). Method ini bisa membangun Vec, String, HashSet, HashMap… jadi ia selalu perlu tahu tujuannya. Perbaikannya: let words: Vec<_> = ...collect(); atau .collect::<Vec<_>>(). Lihat Iterator.

.sum() dan .product(). nums.iter().sum() menghasilkan E0283 dengan the type must implement `Sum<&i32>`. Menjumlahkan i32 bisa menghasilkan i32, tapi tipe angka lain juga bisa jadi hasilnya. Perbaikannya: let total: i32 = nums.iter().sum(); atau .sum::<i32>().

Koleksi kosong di constructor struct. let cache = HashMap::new(); di dalam fungsi new() yang belum selesai kamu tulis. Perbaikannya: tulis tipe lengkapnya sekali (HashMap<String, u32>), atau kembalikan di dalam struct yang tipe field-nya sudah dideklarasikan, karena itu petunjuk yang bisa dipakai Rust.

  • E0308: mismatched types (tipe tidak cocok). Terjadi kalau anotasimu dan nilainya tidak sepakat, seperti let age: u32 = "42";.
  • E0277: the trait bound is not satisfied (trait tidak terpenuhi). Kamu sudah memberi tipe, tapi tipe itu tidak bisa dibangun dengan cara tersebut, seperti let n: Vec<i32> = "1 2".split(' ').collect(); (itu Vec<&str>; parse setiap item dulu).
  • E0599: no method found (method tidak ditemukan), misalnya saat kamu memberi anotasi let n: u32 lalu memanggil method String seperti .trim() padanya.
  • E0004: non-exhaustive patterns (pola belum lengkap), error lain yang perbaikannya dituliskan langsung oleh rustc.
  • Semua error compiler: daftar lengkap halaman error.

Kuis singkat

1. Baris mana yang memperbaiki E0282 pada let ids = Vec::new(); kalau Vec itu dimaksudkan berisi nilai u64?

2. Kenapa let n = "7".parse().unwrap(); butuh tipe, padahal teksnya jelas-jelas angka?

3. Apa arti _ di .collect::<Vec<_>>()?

Skor: 0 / 3