Salta ai contenuti

Errore Rust E0057: this function takes N arguments but M were supplied (closure)

E0057 significa che hai chiamato una closure con il numero sbagliato di argomenti: troppo pochi o troppi. È la versione per le closure di E0061, e il messaggio si legge quasi uguale. La soluzione di solito è rapida: guarda le |...| della closure e passa un valore per ogni parametro. Questa pagina copre anche due parenti stretti che incontrerai con gli iteratori, E0593 ed E0631, perché nascono dalla stessa confusione. Per gli altri errori di questa serie, vedi Errori del compilatore Rust.

error[E0057]: this function takes 2 arguments but 1 argument was supplied
--> src/main.rs:3:17
|
3 | let total = add(1); // ❌ add wants two numbers
| ^^^--- argument #2 of type `i32` is missing
|
note: closure defined here
--> src/main.rs:2:15
|
2 | let add = |a: i32, b: i32| a + b;
| ^^^^^^^^^^^^^^^^
help: provide the argument
|
3 | let total = add(1, /* i32 */); // ❌ add wants two numbers
| +++++++++++

Il compilatore dice: «questa funzione prende 2 argomenti, ma ne è stato fornito 1», e la nota indica la closure. Questo è il programma che lo produce:

fn main() {
let add = |a: i32, b: i32| a + b;
let total = add(1); // ❌ add wants two numbers
println!("{total}");
}

Troppi argomenti danno lo stesso codice, con un’etichetta «unexpected argument» (argomento inatteso) e un help «remove the extra argument» (togli l’argomento in più):

error[E0057]: this function takes 1 argument but 2 arguments were supplied
--> src/main.rs:3:5
|
3 | greet("Ferris", "Corro");
| ^^^^^ ------- unexpected argument #2 of type `&'static str`
|
note: closure defined here

Leggi il messaggio come quello per le funzioni: la prima riga conta (la closure ne prende 2, tu ne hai dato 1), l’etichetta sotto la tua chiamata nomina l’argomento mancante o in più, e help: mostra dove va. /* i32 */ è solo un commento segnaposto che ti dice il tipo. Mettici un valore vero.

E0057 o E0061? La prima riga è la stessa. È la note: a distinguerli:

  • closure defined here: hai chiamato una closure. È E0057.
  • callable defined here: hai chiamato un f generico con un vincolo come F: Fn(i32). Anche questo è E0057.
  • function defined here: una normale fn, un metodo o un puntatore a funzione (let f: fn(i32, i32) -> i32 = add;). È E0061.

In ogni caso la soluzione è la stessa: fai corrispondere la chiamata all’elenco dei parametri.

L’elenco dei parametri di una closure è fisso quanto quello di una funzione. |a: i32, b: i32| prende esattamente due i32, ogni volta. Non ci sono argomenti predefiniti né «passane meno se vuoi». Anche quando ometti i tipi, rustc li ricava dal corpo della closure e dal suo primo utilizzo, e da quel momento restano bloccati.

Dietro le quinte, una closure implementa uno dei trait Fn con un elenco fisso di tipi di argomenti, come Fn(i32, i32) -> i32. Il compilatore controlla ogni chiamata rispetto a quell’elenco, quindi una chiamata con il numero sbagliato non arriva mai al programma in esecuzione. Come funzionano le closure e i loro parametri lo trovi in Closure e Fn, FnMut e FnOnce.

Soluzione 1: Fai corrispondere la chiamata ai parametri della closure

Sezione intitolata “Soluzione 1: Fai corrispondere la chiamata ai parametri della closure”

Sceglila quasi sempre. Vai al punto indicato dalla note:, conta i parametri tra le barre |...| e passa un valore per ciascuno. Se hai più valori che parametri, chiama la closure più di una volta:

fn main() {
let add = |a: i32, b: i32| a + b;
let greet = |name: &str| println!("Ciao, {name}!");
let total = add(1, 2);
println!("{total}");
greet("Ferris");
greet("Corro"); // una chiamata per ogni nome
}

Output:

3
Ciao, Ferris!
Ciao, Corro!
▶ Provalo tu

Se la closure deve davvero prendere un numero diverso di valori, cambia invece la closure. Va benissimo anche così, purché ogni chiamata sia d’accordo con lei.

Soluzione 2: Tuple: una tupla è un solo argomento

Sezione intitolata “Soluzione 2: Tuple: una tupla è un solo argomento”

Scegli questa quando ci sono di mezzo le tuple. |(name, score)| e |name, score| si somigliano, ma sono closure diverse. La prima prende un argomento, una tupla, e lo spacchetta. La seconda prende due argomenti.

Chiamare una closure con tupla passando due valori dà E0057, e rustc suggerisce di aggiungere le parentesi:

error[E0057]: function takes 1 argument but 2 arguments were supplied
--> src/main.rs:3:5
|
3 | show("Ferris", 90);
| ^^^^
|
note: closure parameter defined here
help: wrap these arguments in parentheses to construct a tuple
|
3 | show(("Ferris", 90));
| + +

L’errore opposto capita di continuo con gli iteratori. zip, enumerate e l’iterazione sulle mappe passano alla tua closure una tupla per elemento. Scrivere .map(|name, score| ...) dà un codice diverso, E0593:

error[E0593]: closure is expected to take a single 2-tuple as argument, but it takes 2 distinct arguments
--> src/main.rs:7:10
|
7 | .map(|name, score| format!("{name}: {score}"))
| ^^^ ------------- 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
|
7 - .map(|name, score| format!("{name}: {score}"))
7 + .map(|(name, score)| format!("{name}: {score}"))

Qui rustc dice che si aspettava una closure con una sola tupla di 2 elementi, ma la tua prende 2 argomenti distinti. Entrambi si risolvono allo stesso modo: metti le parentesi dove c’è la tupla.

fn main() {
let names = vec!["Ferris", "Corro"];
let scores = vec![90, 75];
// zip dà una tupla (name, score) per elemento: spacchettala nell'elenco dei parametri
let lines: Vec<String> = names
.iter()
.zip(scores.iter())
.map(|(name, score)| format!("{name}: {score}"))
.collect();
println!("{:?}", lines);
// una closure che prende una tupla si chiama con una tupla
let show = |(name, score): (&str, i32)| println!("{name} ha fatto {score} punti");
show(("Ferris", 90));
}

Output:

["Ferris: 90", "Corro: 75"]
Ferris ha fatto 90 punti
▶ Provalo tu

Lo stesso vale per .enumerate().map(|(i, item)| ...) e per map.iter().for_each(|(key, value)| ...). Vedi Iteratori e Tuple.

Scegli questa quando passi una funzione o una closure a un’altra funzione (una «funzione di ordine superiore») e il numero dei parametri non torna. Qui apply vuole qualcosa che prende un i32, ma add ne prende due. Di nuovo E0593:

error[E0593]: function is expected to take 1 argument, but it takes 2 arguments
--> src/main.rs:10:26
|
5 | fn add(a: i32, b: i32) -> i32 {
| ----------------------------- takes 2 arguments
...
10 | println!("{}", apply(add, 5));
| ----- ^^^ expected function that takes 1 argument
| |
| required by a bound introduced by this call

Non cambiare add. Avvolgila in una piccola closure che ha la forma che apply si aspetta e che completa da sola il valore mancante:

fn apply<F: Fn(i32) -> i32>(f: F, x: i32) -> i32 {
f(x)
}
fn add(a: i32, b: i32) -> i32 {
a + b
}
fn main() {
let bonus = 10;
// prende 1 argomento, chiama add con 2
println!("{}", apply(|x| add(x, bonus), 5));
// prende 1 tupla, chiama add con le sue due parti
let pairs = vec![(1, 2), (3, 4)];
let sums: Vec<i32> = pairs.into_iter().map(|(a, b)| add(a, b)).collect();
println!("{:?}", sums);
}

Output:

15
[3, 7]
▶ Provalo tu

La closure di appoggio prende in prestito bonus dal codice che la circonda, ed è proprio ciò in cui le closure sono brave. Passare una funzione per nome (apply(double, 5)) funziona solo quando i suoi parametri corrispondono già. Vedi Fn, FnMut e FnOnce.

Dentro una funzione generica. fn call_twice<F: Fn(i32)>(f: F) che chiama f(2, 3) dà E0057 con note: callable defined here, che indica Fn(i32). Il vincolo è l’elenco dei parametri. Soluzione: chiama f(2), oppure cambia il vincolo in Fn(i32, i32) se ti servono davvero due valori (e allora la closure di ogni chiamante dovrà prenderne due).

Closure in un Box. let f: Box<dyn Fn(i32, i32) -> i32> = Box::new(|a, b| a + b); e poi f(1) dà E0057 con note: implementation defined here. Soluzione: passa entrambi i valori, f(1, 2).

L’ordinamento. v.sort_by(|a| a.cmp(a)) dà E0593, «closure is expected to take 2 arguments, but it takes 1 argument». sort_by confronta due elementi alla volta. Soluzione: v.sort_by(|a, b| a.cmp(b)). Vedi Ordinamento.

Numero giusto, tipo sbagliato. nums.iter().map(|x: i32| x * 2) dà E0631, «type mismatch in closure arguments» (tipi non corrispondenti negli argomenti della closure), perché .iter() passa &i32. L’help di rustc suggerisce |x: &i32|. La soluzione più semplice: togli l’annotazione, |x| x * 2.

Spacchettare qualcosa che non è una tupla. v.iter().map(|(a, b)| a + b) su un semplice Vec<i32> dà E0308, «expected integer, found (_, _)». Soluzione: usa |(a, b)| solo quando gli elementi sono davvero delle coppie.

Verifica veloce

1. let double = |x: i32| x * 2; viene chiamata come double(2, 3). Cosa segnala rustc?

2. names.iter().zip(ages.iter()).map(|n, a| ...) non compila. Qual è la soluzione?

3. Sia E0057 sia E0061 dicono this function takes 2 arguments but 1 argument was supplied. Come capisci che hai chiamato una closure?

Punteggio: 0 / 3