Salta ai contenuti

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.

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
|

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 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

Guarda i due segni sotto la riga 6:

  • ^^^ indica map. È 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).

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.

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 tutto
▶ Provalo tu

Il _ 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_by confronta due elementi: |a, b|.
  • for_each, map, filter passano un elemento: |item|, oppure |_| se non ti serve.
  • thread::spawn non passa niente: ||, di solito move ||.
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: scrivere
da fare: testare
da fare: rilasciare
***
worker 1 ha finito
▶ Provalo tu

Per 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 call

Puoi 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 tupla
fn 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]
▶ Provalo tu

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.

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.

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