Errore Rust E0593: closure is expected to take a single 2-tuple as argument
E0593 significa che hai passato una closure (o una funzione) a qualcos’altro, come map, sort_by o thread::spawn, e il suo numero di parametri non corrisponde al modo in cui verrà chiamata. Il caso di gran lunga più comune: un iteratore dà alla tua closure una sola tupla, e tu hai scritto due parametri. Quasi sempre si risolve con un paio di parentesi, e di solito rustc ti scrive già la correzione. Se invece sei tu a chiamare la closure con il numero sbagliato di valori, l’errore è E0057. Per gli altri errori di questa serie, vedi Errori del compilatore Rust.
Il messaggio di errore
Sezione intitolata “Il messaggio di errore”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 parameters6 + .map(|(i, name)| format!("{}. {name}", i + 1)) // ❌ two parameters |Il compilatore ti dice: «la closure dovrebbe ricevere una sola tupla di 2 elementi come argomento, ma ne riceve 2 distinti». E ti suggerisce di accettare una tupla invece di argomenti separati.
Ecco il programma che lo provoca:
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);}(Il commento dice: due parametri.)
E0593 può avere anche altre prime righe. Significano tutte «numero sbagliato di parametri»:
error[E0593]: closure is expected to take 2 arguments, but it takes 1 argumenterror[E0593]: closure is expected to take 0 arguments, but it takes 1 argumenterror[E0593]: function is expected to take 1 argument, but it takes 2 argumentserror[E0593]: closure is expected to take 2 distinct arguments, but it takes a single 2-tuple as argumentCosa significa
Sezione intitolata “Cosa significa”Guarda i due segni sotto la riga 6:
^^^indicamap. È lui che chiama la tua closure, e decide quanti valori passarle.---------indica la tua lista di parametri|i, name|. È quello che hai promesso di accettare.
Le due cose non coincidono. enumerate() trasforma ogni elemento in una sola tupla, (indice, elemento), e map passa quella tupla alla tua closure come un argomento. La tua closure ne chiede due separati.
Pensa a una buca delle lettere che accetta una busta alla volta. Dentro la busta puoi mettere due lettere (una tupla), ma la buca non prende due lettere sciolte. |(i, name)| vuol dire «dammi la busta, la apro io qui».
La parola «closure» o «function» nel messaggio ti dice cosa hai passato: una closure scritta sul posto, oppure una fn con un nome, come in .map(add).
Perché Rust si comporta così
Sezione intitolata “Perché Rust si comporta così”Ogni metodo che riceve una closure dice esattamente come la chiamerà. map vuole qualcosa come FnMut(Self::Item) -> B: un argomento, l’elemento. Per enumerate() l’elemento è (usize, &&str): un solo valore, che guarda caso è una tupla. sort_by vuole FnMut(&T, &T) -> Ordering: due argomenti. thread::spawn vuole FnOnce() -> T: nessuno.
Rust non divide mai una tupla in argomenti separati per te, e non impacchetta mai argomenti separati in una tupla. Così la regola resta semplice: quello che dice la firma del metodo è quello che riceve la tua closure. Una volta che sai leggere queste firme, puoi prevedere cosa ti passa qualsiasi metodo degli iteratori.
Trovi di più sui trait Fn che stanno dietro a tutto questo in Trait delle closure, e sulle tuple in Tuple.
Come risolverlo
Sezione intitolata “Come risolverlo”Soluzione 1: scomponi la tupla nella lista dei parametri
Sezione intitolata “Soluzione 1: scomponi la tupla nella lista dei parametri”Scegli questa strada quando il messaggio dice «a single 2-tuple». È la correzione che ti servirà più spesso. Aggiungi le parentesi: |(i, name)| riceve una tupla e dà un nome alle sue due parti. Funziona con enumerate, con zip e quando scorri una mappa:
use std::collections::BTreeMap;
fn main() { let names = vec!["Ferris", "Corro", "Rusty"];
// enumerate dà una tupla (indice, nome) per ogni elemento let lines: Vec<String> = names .iter() .enumerate() .map(|(i, name)| format!("{}. {name}", i + 1)) .collect(); println!("{:?}", lines);
// anche scorrere una mappa dà una tupla (chiave, valore) per elemento 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} frutti in tutto");}Output:
["1. Ferris", "2. Corro", "3. Rusty"]8 frutti in tuttoIl _ all’inizio di _name dice a Rust «lo so che non lo uso». Puoi anche scrivere solo _. La stessa idea applicata a zip è nella pagina di E0057.
Soluzione 2: dai al metodo il numero di parametri con cui chiama
Sezione intitolata “Soluzione 2: dai al metodo il numero di parametri con cui chiama”Scegli questa strada quando il messaggio dice «expected to take N arguments». Controlla cosa passa il metodo, poi scrivi esattamente quel numero di parametri:
sort_byconfronta due elementi:|a, b|.for_each,map,filterpassano un elemento:|item|, oppure|_|se non ti serve.thread::spawnnon passa niente:||, di solitomove ||.
use std::thread;
fn main() { // sort_by confronta due elementi alla volta: due parametri let mut scores = vec![42, 7, 99, 15]; scores.sort_by(|a, b| b.cmp(a)); println!("{:?}", scores);
// for_each passa un elemento: un parametro (usa _ se non ti serve) let tasks = vec!["scrivere", "testare", "rilasciare"]; tasks.iter().for_each(|task| println!("da fare: {task}")); tasks.iter().for_each(|_| print!("*")); println!();
// thread::spawn chiama la closure senza niente: zero parametri let id = 1; let handle = thread::spawn(move || format!("worker {id} ha finito")); println!("{}", handle.join().unwrap());}Output:
[99, 42, 15, 7]da fare: scrivereda fare: testareda fare: rilasciare***worker 1 ha finitoPer passare dati a un thread non aggiungere un parametro: catturali con move, come id qui sopra. Vedi Concorrenza e Ordinamento.
Soluzione 3: avvolgi una funzione con nome, o falle ricevere una tupla
Sezione intitolata “Soluzione 3: avvolgi una funzione con nome, o falle ricevere una tupla”Scegli questa strada quando il messaggio dice «function is expected to take…». Hai passato una funzione per nome, come .map(add), e i suoi parametri non vanno bene. Qui gli elementi sono coppie, ma add riceve due numeri:
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 callPuoi avvolgere la funzione in una piccola closure che scompone la tupla e la chiama, oppure scrivere una versione che riceve direttamente la tupla:
fn add(a: i32, b: i32) -> i32 { a + b}
// una versione che riceve la coppia come una sola tuplafn add_pair((a, b): (i32, i32)) -> i32 { a + b}
fn main() { let pairs = vec![(1, 2), (3, 4), (10, 20)];
// avvolgi: entra una tupla, escono due argomenti let sums: Vec<i32> = pairs.iter().map(|&(a, b)| add(a, b)).collect(); println!("{:?}", sums);
// oppure passa una funzione che riceve già una tupla let sums2: Vec<i32> = pairs.into_iter().map(add_pair).collect(); println!("{:?}", sums2);}Output:
[3, 7, 30][3, 7, 30]Il & in |&(a, b)| serve perché .iter() presta ogni coppia come &(i32, i32), e il pattern copia fuori i due numeri. Di solito la closure che avvolge è la scelta migliore: add resta una normale funzione a due argomenti che il resto del codice può chiamare.
Se una tua funzione generica vuole Fn(i32) -> i32 e le passi un add a due argomenti, ottieni «function is expected to take 1 argument, but it takes 2 arguments». La soluzione è una closure che completa il valore mancante, come mostra la pagina di E0057.
Dove lo incontri di solito
Sezione intitolata “Dove lo incontri di solito”HashMap::retain (al contrario). Quasi tutti i metodi delle mappe ti passano una tupla, ma retain passa chiave e valore come due argomenti separati. Quindi stock.retain(|(name, count)| *count > 0) dà «closure is expected to take 2 distinct arguments, but it takes a single 2-tuple as argument». Soluzione: togli le parentesi, stock.retain(|_name, count| *count > 0). Vec::retain invece riceve un elemento, come al solito.
Dimenticare il parametro. tasks.iter().for_each(|| println!("task")) dà «closure is expected to take 1 argument, but it takes 0 arguments», e rustc suggerisce |_|. Soluzione: |_|, oppure |task| se vuoi usarlo.
I thread. thread::spawn(|id| ...) dà «closure is expected to take 0 arguments, but it takes 1 argument». Soluzione: thread::spawn(move || ...), catturando id dall’esterno.
Un parametro puntatore a funzione. Se la tua funzione riceve f: fn(i32) -> i32 (un semplice puntatore a funzione, non un generico F: Fn), passare un add a due argomenti dà invece E0308, con l’etichetta «incorrect number of function parameters». La soluzione è la stessa: una closure che avvolge, come |x| add(x, 10). Le closure che non catturano niente si possono passare come puntatori fn.
Errori correlati
Sezione intitolata “Errori correlati”- E0057: closure takes N arguments but M were supplied: sei tu a chiamare una closure con il numero sbagliato di valori.
- E0631: type mismatch in closure arguments: il numero è giusto, ma un parametro ha il tipo sbagliato, spesso
i32dove l’iteratore dà&i32. - E0061: function takes N arguments but M were supplied: chiami una normale funzione con il numero sbagliato di argomenti.
- E0308: mismatched types: quello che ottieni con un parametro puntatore
fn(...), o con|(a, b)|quando gli elementi non sono affatto tuple. - E0599: no method found: a volte compare subito dopo E0593, per esempio «the method
collectexists … but its trait bounds were not satisfied». Risolvi prima E0593 e sparisce. - Tutti gli errori del compilatore: l’elenco completo delle pagine sugli errori.
Verifica veloce
Sezione intitolata “Verifica veloce”Verifica veloce
1. names.iter().enumerate().map(|i, name| ...) dà E0593. Qual è la soluzione?
2. Quanti parametri servono alla closure di scores.sort_by(...)?
3. thread::spawn(|id| println!("{id}")) dà E0593. Come porti id dentro il thread?
Punteggio: 0 / 3
Per approfondire
Sezione intitolata “Per approfondire”- E0593 — Rust error codes index (in inglese): la spiegazione ufficiale di questo errore.
- Closures — The Rust Book (in inglese): la sintassi delle closure, come vengono dedotti i tipi dei parametri e i trait
Fn. - As input parameters — Rust by Example (in inglese): come il vincolo
Fndi una funzione decide quale closure accetta. Iterator::enumerate— documentazione della libreria standard (in inglese): mostra che ogni elemento diventa una sola tupla(indice, valore).HashMap::retain— documentazione della libreria standard (in inglese): il metodo delle mappe la cui closure riceve chiave e valore come due argomenti.