Lewati ke konten

Error Rust E0593: closure is expected to take a single 2-tuple as argument

E0593 artinya kamu memberikan sebuah closure (fungsi anonim) atau fungsi ke kode lain, misalnya map, sort_by, atau thread::spawn, dan jumlah parameternya tidak cocok dengan cara closure itu akan dipanggil. Kasus yang paling sering: iterator memberikan satu tuple ke closure-mu, tapi kamu menulis dua parameter. Biasanya perbaikannya cuma satu karakter, dan rustc sering sudah menuliskannya untukmu. Kalau justru kamu yang memanggil closure dengan jumlah nilai yang salah, itu E0057. Untuk error lain di seri ini, lihat Penjelasan error compiler Rust.

error[E0593]: closure is expected to take a single 2-tuple as argument, but it takes 2 distinct arguments
--> src/main.rs:6:10
|
6 | .map(|i, name| format!("{}. {name}", i + 1)) // ❌ two parameters
| ^^^ --------- takes 2 distinct arguments
| |
| expected closure that takes a single 2-tuple as argument
|
help: change the closure to accept a tuple instead of individual arguments
|
6 - .map(|i, name| format!("{}. {name}", i + 1)) // ❌ two parameters
6 + .map(|(i, name)| format!("{}. {name}", i + 1)) // ❌ two parameters
|

Compiler bilang map mengharapkan closure yang menerima satu tuple berisi dua nilai, tapi closure-mu menerima dua argumen terpisah. Ia juga langsung menyarankan perbaikannya: tambahkan tanda kurung, |(i, name)|.

Ini program yang menghasilkan error tersebut:

fn main() {
let names = vec!["Ferris", "Corro", "Rusty"];
let lines: Vec<String> = names
.iter()
.enumerate()
.map(|i, name| format!("{}. {name}", i + 1)) // ❌ two parameters
.collect();
println!("{:?}", lines);
}

E0593 juga bisa muncul dengan beberapa baris pertama lain. Semuanya berarti “jumlah parameternya salah”:

error[E0593]: closure is expected to take 2 arguments, but it takes 1 argument
error[E0593]: closure is expected to take 0 arguments, but it takes 1 argument
error[E0593]: function is expected to take 1 argument, but it takes 2 arguments
error[E0593]: closure is expected to take 2 distinct arguments, but it takes a single 2-tuple as argument

Berturut-turut: diharapkan 2 argumen tapi closure-nya menerima 1; diharapkan 0 tapi menerima 1; sebuah fungsi diharapkan menerima 1 argumen tapi menerima 2; dan kebalikan dari contoh di atas, diharapkan 2 argumen terpisah tapi closure-nya menerima satu tuple.

Lihat dua penanda di bawah baris 6:

  • ^^^ menunjuk ke map. Dialah yang memanggil closure-mu, dan dia yang menentukan berapa nilai yang diberikan.
  • --------- menunjuk ke daftar parameter |i, name|. Itulah yang kamu janjikan untuk diterima.

Keduanya tidak sepakat. enumerate() mengubah setiap item menjadi satu tuple, (index, item), dan map memberikan tuple itu ke closure-mu sebagai satu argumen. Closure-mu malah meminta dua argumen terpisah.

Bayangkan lubang surat di pintu yang hanya bisa menerima satu amplop setiap kali. Kamu boleh memasukkan dua lembar surat ke dalam amplop (sebuah tuple), tapi lubang itu tidak mau menerima dua lembar surat lepas. |(i, name)| artinya “berikan amplopnya, nanti aku buka di sini”.

Kata “closure” atau “function” di pesan error memberi tahu apa yang kamu berikan: closure yang ditulis langsung di tempat, atau fn bernama seperti .map(add).

Setiap method yang menerima closure menyatakan dengan tepat bagaimana closure itu akan dipanggil. map menginginkan sesuatu seperti FnMut(Self::Item) -> B: satu argumen, yaitu item-nya. Untuk enumerate(), item-nya adalah (usize, &&str). Itu satu nilai yang kebetulan berupa tuple. sort_by menginginkan FnMut(&T, &T) -> Ordering: dua argumen. thread::spawn menginginkan FnOnce() -> T: tanpa argumen sama sekali.

Rust tidak pernah memecah tuple menjadi argumen terpisah untukmu, dan tidak pernah membungkus argumen terpisah menjadi tuple. Dengan begitu aturannya tetap sederhana: apa yang tertulis di signature method, itulah yang diterima closure-mu. Begitu kamu bisa membaca signature seperti ini, kamu bisa menebak apa yang diberikan oleh method iterator mana pun.

Kamu bisa membaca lebih lanjut tentang trait Fn di balik semua ini di Trait closure, dan tentang tuple di Destructuring tuple.

Perbaikan 1: Bongkar tuple-nya di daftar parameter

Section titled “Perbaikan 1: Bongkar tuple-nya di daftar parameter”

Pilih ini kalau pesannya bilang “a single 2-tuple”. Inilah perbaikan yang paling sering kamu butuhkan. Tambahkan tanda kurung: |(i, name)| menerima satu tuple dan memberi nama pada kedua bagiannya. Cara ini bekerja untuk enumerate, zip, dan iterasi atas sebuah map:

use std::collections::BTreeMap;
fn main() {
let names = vec!["Ferris", "Corro", "Rusty"];
// enumerate memberikan satu tuple (index, name) untuk setiap item
let lines: Vec<String> = names
.iter()
.enumerate()
.map(|(i, name)| format!("{}. {name}", i + 1))
.collect();
println!("{:?}", lines);
// iterasi atas map juga memberikan satu tuple (key, value) untuk setiap item
let mut stock = BTreeMap::new();
stock.insert("apples", 3);
stock.insert("pears", 0);
stock.insert("plums", 5);
let total: i32 = stock.iter().map(|(_name, count)| *count).sum();
println!("{total} buah tersedia");
}

Output:

["1. Ferris", "2. Corro", "3. Rusty"]
8 buah tersedia
▶ Coba sendiri

Tanda _ di awal _name memberi tahu Rust “aku tahu ini tidak kupakai”. Kamu juga bisa menulis _ saja. Ide yang sama untuk zip ditunjukkan di halaman E0057.

Perbaikan 2: Tulis parameter sebanyak yang diberikan method-nya

Section titled “Perbaikan 2: Tulis parameter sebanyak yang diberikan method-nya”

Pilih ini kalau pesannya bilang “expected to take N arguments”. Cek apa yang diberikan method itu, lalu tulis parameter tepat sebanyak itu:

  • sort_by membandingkan dua item: |a, b|.
  • for_each, map, filter memberikan satu item: |item|, atau |_| kalau kamu tidak membutuhkannya.
  • thread::spawn tidak memberikan apa-apa: ||, biasanya move ||.
use std::thread;
fn main() {
// sort_by membandingkan dua item sekaligus: dua parameter
let mut scores = vec![42, 7, 99, 15];
scores.sort_by(|a, b| b.cmp(a));
println!("{:?}", scores);
// for_each memberikan satu item: satu parameter (pakai _ kalau tidak dibutuhkan)
let tasks = vec!["write", "test", "ship"];
tasks.iter().for_each(|task| println!("tugas: {task}"));
tasks.iter().for_each(|_| print!("*"));
println!();
// thread::spawn memanggil closure tanpa argumen: nol parameter
let id = 1;
let handle = thread::spawn(move || format!("worker {id} selesai"));
println!("{}", handle.join().unwrap());
}

Output:

[99, 42, 15, 7]
tugas: write
tugas: test
tugas: ship
***
worker 1 selesai
▶ Coba sendiri

Untuk memberikan data ke sebuah thread, jangan tambahkan parameter. Tangkap datanya dengan move, seperti id di atas. Lihat Konkurensi dan Mengurutkan dan mencari.

Perbaikan 3: Bungkus fungsi bernama, atau buat fungsi yang menerima tuple

Section titled “Perbaikan 3: Bungkus fungsi bernama, atau buat fungsi yang menerima tuple”

Pilih ini kalau pesannya bilang “function is expected to take…”. Kamu memberikan fungsi lewat namanya, seperti .map(add), dan parameternya tidak cocok. Di sini item-nya berupa pasangan, tapi add menerima dua angka:

error[E0593]: function is expected to take a single 2-tuple as argument, but it takes 2 distinct arguments
--> src/main.rs:7:48
|
1 | fn add(a: i32, b: i32) -> i32 {
| ----------------------------- takes 2 distinct arguments
...
7 | let sums: Vec<i32> = pairs.into_iter().map(add).collect();
| --- ^^^ expected function that takes a single 2-tuple as argument
| |
| required by a bound introduced by this call

Rustc menunjuk ke add (menerima dua argumen terpisah) dan ke map (yang akan memanggilnya dengan satu tuple).

Kamu bisa membungkus fungsi itu dalam closure kecil yang membongkar tuple lalu memanggilnya, atau menulis versi yang langsung menerima tuple:

fn add(a: i32, b: i32) -> i32 {
a + b
}
// versi yang menerima pasangan sebagai satu tuple
fn add_pair((a, b): (i32, i32)) -> i32 {
a + b
}
fn main() {
let pairs = vec![(1, 2), (3, 4), (10, 20)];
// bungkus: satu tuple masuk, dua argumen keluar
let sums: Vec<i32> = pairs.iter().map(|&(a, b)| add(a, b)).collect();
println!("{:?}", sums);
// atau berikan fungsi yang memang sudah menerima satu tuple
let sums2: Vec<i32> = pairs.into_iter().map(add_pair).collect();
println!("{:?}", sums2);
}

Output:

[3, 7, 30]
[3, 7, 30]
▶ Coba sendiri

Tanda & di |&(a, b)| ada karena .iter() meminjamkan setiap pasangan sebagai &(i32, i32), dan pattern itu menyalin kedua angkanya keluar. Pembungkus biasanya pilihan yang lebih baik: add tetap fungsi dua argumen biasa yang bisa dipanggil oleh bagian kode lain.

Kalau fungsi generik buatanmu sendiri meminta Fn(i32) -> i32 dan kamu memberikan add yang menerima dua argumen, kamu akan mendapat “function is expected to take 1 argument, but it takes 2 arguments”. Perbaikannya adalah pembungkus yang mengisi nilai tambahannya, seperti di Perbaikan 3 halaman E0057.

HashMap::retain (kebalikannya). Kebanyakan method map memberimu sebuah tuple, tapi retain memberikan key dan value sebagai dua argumen terpisah. Jadi stock.retain(|(name, count)| *count > 0) memberi “closure is expected to take 2 distinct arguments, but it takes a single 2-tuple as argument”. Perbaikannya: hapus tanda kurungnya, stock.retain(|_name, count| *count > 0). Vec::retain menerima satu item, seperti biasa.

Lupa menulis parameter. tasks.iter().for_each(|| println!("task")) memberi “closure is expected to take 1 argument, but it takes 0 arguments”, dan rustc menyarankan |_|. Perbaikannya: |_|, atau |task| kalau kamu ingin memakainya.

Thread. thread::spawn(|id| ...) memberi “closure is expected to take 0 arguments, but it takes 1 argument”. Perbaikannya: thread::spawn(move || ...) dan tangkap id dari luar.

Parameter function pointer. Kalau fungsimu menerima f: fn(i32) -> i32 (function pointer biasa, bukan generik F: Fn), memberikan add yang menerima dua argumen malah menghasilkan E0308, dengan label “incorrect number of function parameters” (jumlah parameter fungsi salah). Perbaikannya sama: closure pembungkus seperti |x| add(x, 10). Closure yang tidak menangkap apa pun bisa diberikan sebagai pointer fn.

Kuis singkat

1. names.iter().enumerate().map(|i, name| ...) memberi E0593. Apa perbaikannya?

2. Berapa parameter yang dibutuhkan closure untuk scores.sort_by(...)?

3. thread::spawn(|id| println!("{id}")) memberi E0593. Bagaimana caranya memasukkan id ke dalam thread?

Skor: 0 / 3