Lewati ke konten

Error Rust E0631: type mismatch in closure arguments (tipe argumen salah)

E0631 artinya closure (fungsi anonim) milikmu punya jumlah parameter yang benar, tapi setidaknya satu parameternya bertipe salah. Hampir selalu, bedanya cuma satu &: kamu menulis |x: i32|, padahal iterator meminjamkan &i32. Perbaikan tercepat sering kali adalah menghapus anotasi tipenya dan membiarkan Rust menyimpulkannya sendiri. Halaman ini menunjukkan perbaikan itu dulu, lalu menjelaskan dari mana & itu datang, supaya lain kali kamu bisa menebaknya. Untuk error lain di seri ini, lihat Penjelasan error compiler Rust.

error[E0631]: type mismatch in closure arguments
--> src/main.rs:3:43
|
3 | let doubled: Vec<i32> = prices.iter().map(|p: i32| p * 2).collect(); // ❌ items are &i32
| ^^^ -------- found signature defined here
| |
| expected due to this
|
= note: expected closure signature `fn(&{integer}) -> _`
found closure signature `fn(i32) -> _`
note: required by a bound in `map`
help: consider adjusting the signature so it borrows its argument
|
3 | let doubled: Vec<i32> = prices.iter().map(|p: &i32| p * 2).collect(); // ❌ items are &i32
| +

Compiler bilang map mengharapkan closure yang menerima referensi ke sebuah integer, tapi closure-mu menerima i32 langsung. Bagian help: menyarankan untuk menambahkan satu &.

Ini program yang menghasilkan error tersebut:

fn main() {
let prices = vec![120, 45, 300];
let doubled: Vec<i32> = prices.iter().map(|p: i32| p * 2).collect(); // ❌ items are &i32
println!("{:?}", doubled);
}

rustc juga mencetak error kedua yang lebih panjang tepat setelahnya: the method `collect` exists for struct `Map<...>`, but its trait bounds were not satisfied (E0599). Itu hanya efek samping. Perbaiki E0631, maka error itu ikut hilang.

Dua baris note itulah inti pesannya. Bacalah sebagai “method-nya mau ini, kamu menulis itu”:

  • expected fn(&{integer}) -> _: map akan memanggil closure-mu dengan sebuah referensi ke integer. {integer} artinya “suatu tipe integer yang belum dipastikan Rust”, dan _ artinya “tipe kembalian apa saja”.
  • found fn(i32) -> _: closure-mu bilang ia menerima i32 secara langsung (by value).

Kalau dijajarkan, satu-satunya bedanya adalah &. Baris help: menunjukkan perbaikan satu karakter itu.

Bayangkan tempat penitipan jaket. Kamu menyerahkan jaketmu dan mendapat karcis. .iter() bekerja dengan cara yang sama: ia tidak memberimu item-nya, tapi karcis untuk item itu (referensi). Closure yang bersikeras ingin jaketnya sendiri tidak akan mau menerima karcis.

Setiap method iterator menentukan dengan tepat tipe apa yang diberikan ke closure-mu, dan tipe itu tergantung bagaimana kamu membuat iterator-nya:

  • v.iter() meminjamkan setiap item: &T.
  • v.into_iter() (pada Vec milikmu) menyerahkan setiap item: T.
  • filter dan find meminjamkan item itu sekali lagi, jadi di atas .iter() kamu mendapat &&T.
  • sort_by meminjamkan dua item: &T dan &T.

Kalau kamu tidak menulis tipe parameternya, Rust mengisinya dari method itu, jadi selalu cocok. Kalau kamu menulis tipenya sendiri, Rust memegang kata-katamu dan memeriksanya. Rust tidak akan diam-diam menyalin nilainya atau mengubah referensi menjadi nilai yang dimiliki, karena itu akan mengubah perilaku kodemu (dan untuk String, salinan memakan memori dan waktu). Pelajaran Iterator menunjukkan tiga cara mendapatkan iterator, dan Borrowing (peminjaman) menjelaskan referensi.

Coba ini dulu. Parameter closure hampir tidak pernah butuh tipe: Rust tahu persis apa yang diberikan map, filter, dan sort_by. Hapus : i32, dan kodenya bisa dikompilasi:

fn main() {
let prices = vec![120, 45, 300];
let ages = vec![12, 34, 17, 50];
let mut scores = vec![42, 7, 99, 15];
// tanpa anotasi tipe: Rust memakai apa yang benar-benar diberikan iterator
let doubled: Vec<i32> = prices.iter().map(|p| p * 2).collect();
let adults: Vec<&i32> = ages.iter().filter(|age| **age >= 18).collect();
scores.sort_by(|a, b| b.cmp(a));
println!("{:?}", doubled);
println!("{:?}", adults);
println!("{:?}", scores);
}

Output:

[240, 90, 600]
[34, 50]
[99, 42, 15, 7]
▶ Coba sendiri

p * 2 bisa dipakai pada &i32 karena operator angka di Rust juga menerima referensi. Di dalam filter, age bertipe &&i32, jadi **age mengikuti kedua referensi untuk sampai ke angkanya. Perbandingan seperti b.cmp(a) langsung bekerja pada referensi.

Perbaikan 2: Tulis tipe yang benar-benar diberikan method-nya

Section titled “Perbaikan 2: Tulis tipe yang benar-benar diberikan method-nya”

Pilih ini kalau kamu ingin tipenya tetap terlihat, misalnya supaya rangkaian method yang panjang lebih mudah dibaca. Ikuti help: dari rustc dan tambahkan &. Kamu juga bisa menaruh & di pattern: |&p| membongkar referensinya dan memberimu salinan biasa dari angkanya (ini bekerja untuk tipe Copy seperti integer):

fn main() {
let prices = vec![120, 45, 300];
let ages = vec![12, 34, 17, 50];
let mut scores = vec![42, 7, 99, 15];
// tulis tipe sebenarnya: & untuk .iter(), && di dalam filter
let doubled: Vec<i32> = prices.iter().map(|p: &i32| p * 2).collect();
let adults: Vec<&i32> = ages.iter().filter(|age: &&i32| **age >= 18).collect();
scores.sort_by(|a: &i32, b: &i32| b.cmp(a));
// atau salin angkanya keluar dengan pattern &
let tax: Vec<i32> = prices.iter().map(|&p| p / 10).collect();
println!("{:?}", doubled);
println!("{:?}", adults);
println!("{:?}", scores);
println!("{:?}", tax);
}

Output:

[240, 90, 600]
[34, 50]
[99, 42, 15, 7]
[12, 4, 30]
▶ Coba sendiri

Perhatikan bedanya: |p: &i32| artinya “p adalah referensi”, sedangkan |&p| artinya “aku menerima referensi, berikan isinya padaku”.

Perbaikan 3: Ubah apa yang diberikan iterator-nya

Section titled “Perbaikan 3: Ubah apa yang diberikan iterator-nya”

Pilih ini kalau kamu memang butuh nilainya, bukan referensi. Ada dua cara:

  • .iter().copied() mengubah setiap &i32 menjadi i32. Pakai untuk tipe Copy kecil seperti angka, char, dan bool. (.cloned() melakukan hal yang sama untuk tipe seperti String, dengan cara meng-clone.)
  • .into_iter() pada Vec memberikan item-nya sendiri. Setelah itu Vec-nya habis terpakai, jadi pilih ini kalau kamu tidak membutuhkannya lagi.
fn main() {
let prices = vec![120, 45, 300];
// .copied() mengubah item &i32 menjadi item i32
let doubled: Vec<i32> = prices.iter().copied().map(|p: i32| p * 2).collect();
println!("{:?}", doubled);
// .into_iter() pada Vec memberikan item-nya sendiri (dan menghabiskan Vec-nya)
let names = vec![String::from("ferris"), String::from("corro")];
let loud: Vec<String> = names.into_iter().map(|name: String| name.to_uppercase()).collect();
println!("{:?}", loud);
}

Output:

[240, 90, 600]
["FERRIS", "CORRO"]
▶ Coba sendiri

Kalau kamu masih butuh names setelah loop, tetap pakai .iter() dan terima &String. Memindahkan item keluar dari Vec yang hanya kamu pinjam adalah error lain, E0507.

Perbaikan 4: Buat fungsi bernama menerima apa yang diberikan iterator

Section titled “Perbaikan 4: Buat fungsi bernama menerima apa yang diberikan iterator”

Pilih ini kalau kamu memberikan fungsi lewat namanya, seperti .map(shout). Pesannya lalu bilang “function”, bukan “closure”:

error[E0631]: type mismatch in function arguments
--> src/main.rs:7:51
|
1 | fn shout(word: &String) -> String {
| --------------------------------- found signature defined here
...
7 | let loud: Vec<String> = words.into_iter().map(shout).collect();
| --- ^^^^^ expected due to this
| |
| required by a bound introduced by this call
|
= note: expected function signature `fn(&str) -> _`
found function signature `fn(&String) -> _`

Item-nya bertipe &str, tapi shout meminta &String. Kebiasaan yang baik: buat fungsi yang hanya membaca teks menerima &str. Dengan begitu fungsi itu menerima string literal, &String, dan lainnya. Kalau iterator menambahkan & ekstra (seperti filter), bungkus pemanggilannya dalam closure pendek. Di dalam pemanggilan fungsi biasa, Rust membuang & ekstra untukmu (ini disebut deref coercion), tapi Rust tidak bisa melakukannya saat kamu memberikan fungsi lewat namanya:

// terima &str: bisa untuk string literal, &String, dan lainnya
fn shout(word: &str) -> String {
word.to_uppercase()
}
fn is_long(word: &str) -> bool {
word.len() > 4
}
fn main() {
let words = vec!["crab", "rustacean", "ferris"];
// item-nya &str, jadi shout bisa diberikan lewat namanya
let loud: Vec<String> = words.iter().copied().map(shout).collect();
println!("{:?}", loud);
// filter memberikan &&str: closure kecil membiarkan Rust mengubahnya untukmu
let long: Vec<&str> = words.iter().copied().filter(|w| is_long(w)).collect();
println!("{:?}", long);
}

Output:

["CRAB", "RUSTACEAN", "FERRIS"]
["rustacean", "ferris"]
▶ Coba sendiri

Selengkapnya tentang &str dan String di String.

Mengurutkan dengan parameter bertipe. scores.sort_by(|a: i32, b: i32| b.cmp(&a)) memberi E0631 dengan expected closure signature `for<'a, 'b> fn(&'a {integer}, &'b {integer}) -> _`. Bagian for<'a, 'b> hanya berarti “untuk borrow apa pun”. Perbaikannya: |a, b| b.cmp(a). Lihat Mengurutkan dan mencari.

Entri map dengan tipe yang dimiliki. stock.iter().map(|(name, count): (String, i32)| ...) pada HashMap<String, i32> memberi expected closure signature `fn((&String, &_)) -> _`. .iter() pada map meminjamkan key maupun value-nya. Perbaikannya: hapus anotasinya, atau tulis (&String, &i32).

Fungsi generik buatanmu sendiri. Kalau fn apply<F: Fn(i32) -> i32>(f: F, x: i32) mendapat closure |x: f64| x / 2.0, rustc melaporkan expected closure signature `fn(i32) -> _` dan found closure signature `fn(f64) -> _`, sambil menunjuk ke bound-nya. Perbaikannya: buat closure-nya menerima i32 (lalu konversi di dalamnya, x as f64 / 2.0), atau ubah bound-nya kalau yang kamu maksud memang f64. Lihat Trait closure.

for_each dengan String. words.iter().for_each(|w: String| ...) pada Vec<&str> mengharapkan fn(&&str). &str bukan String. Perbaikannya: |w|, atau |w: &&str|.

Kuis singkat

1. let v = vec![1, 2, 3]; Tipe apa yang diterima closure di v.iter().map(|x| ...)?

2. Note-nya bilang expected closure signature fn(&{integer}) -> _, found closure signature fn(i32) -> _. Apa perbaikan tercepatnya?

3. Apa yang dilakukan |&p| p * 2 di dalam prices.iter().map(...)?

Skor: 0 / 3