Aller au contenu

Erreur Rust E0057 : this function takes N arguments but M were supplied (closures)

E0057 veut dire que vous avez appelé une closure (fonction anonyme) avec le mauvais nombre d’arguments : trop peu ou trop. C’est la version closure de E0061, et le message est presque identique. La correction est en général rapide : regardez les |...| de la closure et passez une valeur par paramètre. Cette page couvre aussi deux proches parentes que vous croiserez avec les itérateurs, E0593 et E0631, parce qu’elles viennent de la même confusion. Pour les autres erreurs de cette série, voir Les erreurs du compilateur Rust.

error[E0057]: this function takes 2 arguments but 1 argument was supplied
--> src/main.rs:3:17
|
3 | let total = add(1); // ❌ add veut deux nombres
| ^^^--- 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 veut deux nombres
| +++++++++++

En français : « cette fonction prend 2 arguments, mais 1 seul a été fourni ». L’étiquette indique que l’argument n° 2, de type i32, manque, et l’aide montre où l’ajouter.

Voici le programme qui la provoque :

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

Trop d’arguments donne le même code, avec une étiquette « unexpected argument » (argument inattendu) et une aide « remove the extra argument » (retirez l’argument en trop) :

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

Lisez le message comme celui des fonctions : la première ligne compte (la closure en prend 2, vous en avez donné 1), l’étiquette sous votre appel nomme l’argument manquant ou en trop, et help: montre où il doit aller. /* i32 */ n’est qu’un commentaire qui vous indique le type. Mettez une vraie valeur à sa place.

E0057 ou E0061 ? La première ligne est la même. C’est la note: qui fait la différence :

  • closure defined here : vous avez appelé une closure. C’est E0057.
  • callable defined here : vous avez appelé un f générique avec une contrainte comme F: Fn(i32). C’est aussi E0057.
  • function defined here : une fn normale, une méthode, ou un pointeur de fonction (let f: fn(i32, i32) -> i32 = add;). C’est E0061.

Dans tous les cas, la correction est la même : faites correspondre l’appel à la liste des paramètres.

La liste des paramètres d’une closure est aussi figée que celle d’une fonction. |a: i32, b: i32| prend exactement deux i32, à chaque fois. Il n’y a pas d’arguments par défaut ni de « passez-en moins si vous voulez ». Même quand vous omettez les types, rustc les déduit du corps de la closure et de sa première utilisation, et ensuite ils sont fixés.

Sous le capot, une closure implémente l’un des traits Fn avec une liste fixe de types d’arguments, comme Fn(i32, i32) -> i32. Le compilateur vérifie chaque appel par rapport à cette liste : un appel avec le mauvais nombre d’arguments n’atteint donc jamais votre programme en cours d’exécution. Le fonctionnement des closures et de leurs paramètres est expliqué dans Les closures et Les traits de closure.

Solution 1 : faire correspondre l’appel aux paramètres de la closure

Section intitulée « Solution 1 : faire correspondre l’appel aux paramètres de la closure »

Choisissez cette solution presque à chaque fois. Allez à l’emplacement indiqué par la note:, comptez les paramètres entre les barres |...|, et passez une valeur pour chacun. Si vous avez plus de valeurs que de paramètres, appelez la closure plusieurs fois :

fn main() {
let add = |a: i32, b: i32| a + b;
let greet = |name: &str| println!("Bonjour, {name} !");
let total = add(1, 2);
println!("{total}");
greet("Ferris");
greet("Corro"); // un appel par nom
}

Sortie :

3
Bonjour, Ferris !
Bonjour, Corro !
▶ Essayez par vous-même

Si la closure doit vraiment prendre un autre nombre de valeurs, modifiez plutôt la closure. C’est très bien aussi, du moment que chaque appel est d’accord avec elle.

Solution 2 : les tuples (un tuple est un seul argument)

Section intitulée « Solution 2 : les tuples (un tuple est un seul argument) »

Choisissez cette solution quand des tuples sont en jeu. |(name, score)| et |name, score| se ressemblent, mais ce sont deux closures différentes. La première prend un argument, un tuple, et le décompose. La seconde prend deux arguments.

Appeler une closure à tuple avec deux valeurs donne E0057, et rustc suggère d’ajouter les parenthèses :

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’aide dit : « entourez ces arguments de parenthèses pour construire un tuple ».

L’erreur inverse arrive tout le temps avec les itérateurs. zip, enumerate et le parcours d’une map donnent à votre closure un tuple par élément. Écrire .map(|name, score| ...) donne un autre code, 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}"))

En français : « la closure devrait prendre un seul tuple de 2 éléments comme argument, mais elle prend 2 arguments distincts ». L’aide propose de lui faire accepter un tuple.

Les deux ont la même correction : mettez les parenthèses là où se trouve le tuple.

fn main() {
let names = vec!["Ferris", "Corro"];
let scores = vec![90, 75];
// zip donne un tuple (name, score) par élément : décomposez-le dans la liste des paramètres
let lines: Vec<String> = names
.iter()
.zip(scores.iter())
.map(|(name, score)| format!("{name}: {score}"))
.collect();
println!("{:?}", lines);
// une closure qui prend un tuple s'appelle avec un seul tuple
let show = |(name, score): (&str, i32)| println!("{name} a obtenu {score}");
show(("Ferris", 90));
}

Sortie :

["Ferris: 90", "Corro: 75"]
Ferris a obtenu 90
▶ Essayez par vous-même

C’est pareil pour .enumerate().map(|(i, item)| ...) et map.iter().for_each(|(key, value)| ...). Voir Les itérateurs et Les tuples.

Solution 3 : adapter avec une closure d’enrobage

Section intitulée « Solution 3 : adapter avec une closure d’enrobage »

Choisissez cette solution quand vous passez une fonction ou une closure à une autre fonction (une « fonction d’ordre supérieur ») et que le nombre de paramètres ne correspond pas. Ici, apply veut quelque chose qui prend un i32, mais add en prend deux. Ça donne de nouveau 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

Autrement dit : apply attend une fonction à 1 argument, mais add en prend 2.

Ne modifiez pas add. Enveloppez-la dans une petite closure qui a la forme qu’attend apply et qui fournit elle-même la valeur manquante :

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;
// prend 1 argument, appelle add avec 2
println!("{}", apply(|x| add(x, bonus), 5));
// prend 1 tuple, appelle add avec ses deux parties
let pairs = vec![(1, 2), (3, 4)];
let sums: Vec<i32> = pairs.into_iter().map(|(a, b)| add(a, b)).collect();
println!("{:?}", sums);
}

Sortie :

15
[3, 7]
▶ Essayez par vous-même

La closure d’enrobage emprunte bonus au code qui l’entoure, et c’est exactement ce que les closures savent bien faire. Passer une fonction par son nom (apply(double, 5)) ne fonctionne que si ses paramètres correspondent déjà. Voir Les traits de closure.

Dans une fonction générique. Une fn call_twice<F: Fn(i32)>(f: F) qui appelle f(2, 3) donne E0057 avec note: callable defined here, qui pointe vers Fn(i32). La contrainte, c’est la liste des paramètres. Correction : appelez f(2), ou changez la contrainte en Fn(i32, i32) si vous avez vraiment besoin de deux valeurs (alors la closure de chaque appelant devra en prendre deux).

Les closures dans une Box. let f: Box<dyn Fn(i32, i32) -> i32> = Box::new(|a, b| a + b); puis f(1) donne E0057 avec note: implementation defined here. Correction : passez les deux valeurs, f(1, 2).

Le tri. v.sort_by(|a| a.cmp(a)) donne E0593, « closure is expected to take 2 arguments, but it takes 1 argument ». sort_by compare deux éléments à la fois. Correction : v.sort_by(|a, b| a.cmp(b)). Voir Le tri.

Le bon nombre, mais le mauvais type. nums.iter().map(|x: i32| x * 2) donne E0631, « type mismatch in closure arguments » (types incompatibles dans les arguments de la closure), parce que .iter() fournit des &i32. L’aide de rustc suggère |x: &i32|. Correction la plus simple : retirez l’annotation, |x| x * 2.

Décomposer quelque chose qui n’est pas un tuple. v.iter().map(|(a, b)| a + b) sur un simple Vec<i32> donne E0308, « expected integer, found (_, _) ». Correction : n’utilisez |(a, b)| que si les éléments sont vraiment des paires.

Petit quiz

1. let double = |x: i32| x * 2; est appelée avec double(2, 3). Que signale rustc ?

2. names.iter().zip(ages.iter()).map(|n, a| ...) ne compile pas. Quelle est la correction ?

3. E0057 et E0061 disent toutes les deux this function takes 2 arguments but 1 argument was supplied. Comment savoir que vous avez appelé une closure ?

Score : 0 / 3