Erreur Rust E0593 : closure is expected to take a single 2-tuple as argument
E0593 veut dire que vous avez transmis une closure (ou une fonction) à autre chose, comme map, sort_by ou thread::spawn, et que son nombre de paramètres ne correspond pas à la façon dont elle sera appelée. Le cas de loin le plus fréquent : un itérateur donne un seul tuple à votre closure, et vous avez écrit deux paramètres. La plupart du temps, la correction tient en deux caractères, et rustc vous l’écrit souvent lui-même. Si c’est vous qui appelez la closure avec le mauvais nombre de valeurs, c’est plutôt E0057. Pour les autres erreurs de cette série, consultez les erreurs du compilateur Rust.
Le message d’erreur
Section intitulée « Le message d’erreur »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 |Le compilateur vous dit : « la closure devrait prendre un seul tuple de 2 éléments comme argument, mais elle prend 2 arguments distincts ». L’aide (help:) vous propose de faire accepter un tuple à la closure.
Voici le programme qui la provoque :
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);}(Le commentaire dit : deux paramètres.)
E0593 peut aussi commencer par d’autres premières lignes. Elles veulent toutes dire « mauvais nombre de paramètres » :
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 argumentDans l’ordre : la closure devrait prendre 2 arguments mais en prend 1 ; elle devrait en prendre 0 mais en prend 1 ; la fonction devrait en prendre 1 mais en prend 2 ; la closure devrait prendre 2 arguments distincts mais prend un seul tuple.
Ce que ça veut dire
Section intitulée « Ce que ça veut dire »Regardez les deux marqueurs sous la ligne 6 :
^^^pointe versmap. C’est lui qui appelle votre closure, et c’est lui qui décide combien de valeurs il lui passe.---------pointe vers votre liste de paramètres|i, name|. C’est ce que vous avez promis d’accepter.
Les deux ne sont pas d’accord. enumerate() transforme chaque élément en un seul tuple, (index, élément), et map passe ce tuple à votre closure comme un seul argument. Votre closure, elle, en demande deux séparés.
Imaginez une fente de boîte aux lettres qui n’accepte qu’une enveloppe à la fois. Vous pouvez mettre deux lettres dans l’enveloppe (un tuple), mais la fente ne prendra pas deux lettres en vrac. Écrire |(i, name)|, c’est dire : « donnez-moi l’enveloppe, je l’ouvre tout de suite ».
Le mot « closure » ou « function » dans le message vous indique ce que vous avez passé : une closure écrite sur place, ou une fn nommée comme dans .map(add).
Pourquoi Rust fait ça
Section intitulée « Pourquoi Rust fait ça »Chaque méthode qui prend une closure dit exactement comment elle va l’appeler. map attend quelque chose comme FnMut(Self::Item) -> B : un seul argument, l’élément. Avec enumerate(), l’élément est un (usize, &&str). C’est une seule valeur, qui se trouve être un tuple. sort_by attend FnMut(&T, &T) -> Ordering : deux arguments. thread::spawn attend FnOnce() -> T : aucun.
Rust ne découpe jamais un tuple en arguments séparés à votre place, et ne regroupe jamais des arguments séparés dans un tuple. La règle reste donc simple : ce que dit la signature de la méthode, c’est ce que reçoit votre closure. Une fois que vous savez lire ces signatures, vous pouvez prévoir ce que n’importe quelle méthode d’itérateur vous donnera.
Pour en savoir plus sur les traits Fn derrière tout ça, voir Les traits des closures, et sur les tuples, Déstructurer un tuple.
Comment corriger
Section intitulée « Comment corriger »Solution 1 : décomposer le tuple dans la liste des paramètres
Section intitulée « Solution 1 : décomposer le tuple dans la liste des paramètres »Choisissez-la quand le message parle d’« a single 2-tuple ». C’est la correction dont vous aurez besoin le plus souvent. Ajoutez des parenthèses : |(i, name)| prend un seul tuple et donne un nom à ses deux parties. Ça marche pour enumerate, pour zip et pour le parcours d’une map :
use std::collections::BTreeMap;
fn main() { let names = vec!["Ferris", "Corro", "Rusty"];
// enumerate donne un tuple (index, nom) par élément let lines: Vec<String> = names .iter() .enumerate() .map(|(i, name)| format!("{}. {name}", i + 1)) .collect(); println!("{:?}", lines);
// parcourir une map donne aussi un tuple (clé, valeur) par élément let mut stock = BTreeMap::new(); stock.insert("pommes", 3); stock.insert("poires", 0); stock.insert("prunes", 5); let total: i32 = stock.iter().map(|(_name, count)| *count).sum(); println!("{total} fruits au total");}Sortie :
["1. Ferris", "2. Corro", "3. Rusty"]8 fruits au totalLe _ au début de _name dit à Rust : « je sais que je ne m’en sers pas ». Vous pouvez aussi écrire simplement _. La même idée appliquée à zip est montrée sur la page E0057.
Solution 2 : donner autant de paramètres que la méthode en passe
Section intitulée « Solution 2 : donner autant de paramètres que la méthode en passe »Choisissez-la quand le message dit « expected to take N arguments ». Regardez ce que la méthode passe, puis écrivez exactement ce nombre de paramètres :
sort_bycompare deux éléments :|a, b|.for_each,mapetfilterpassent un élément :|item|, ou|_|si vous n’en avez pas besoin.thread::spawnne passe rien :||, en généralmove ||.
use std::thread;
fn main() { // sort_by compare deux éléments à la fois : deux paramètres let mut scores = vec![42, 7, 99, 15]; scores.sort_by(|a, b| b.cmp(a)); println!("{:?}", scores);
// for_each donne un élément : un paramètre (_ si vous n'en avez pas besoin) let tasks = vec!["écrire", "tester", "livrer"]; tasks.iter().for_each(|task| println!("à faire : {task}")); tasks.iter().for_each(|_| print!("*")); println!();
// thread::spawn appelle la closure sans rien : zéro paramètre let id = 1; let handle = thread::spawn(move || format!("thread {id} terminé")); println!("{}", handle.join().unwrap());}Sortie :
[99, 42, 15, 7]à faire : écrireà faire : testerà faire : livrer***thread 1 terminéPour faire entrer des données dans un thread, n’ajoutez pas de paramètre. Capturez-les avec move, comme id ci-dessus. Voir La concurrence et Le tri.
Solution 3 : enrober une fonction nommée, ou lui faire prendre un tuple
Section intitulée « Solution 3 : enrober une fonction nommée, ou lui faire prendre un tuple »Choisissez-la quand le message dit « function is expected to take… ». Vous avez passé une fonction par son nom, comme .map(add), et ses paramètres ne collent pas. Ici, les éléments sont des paires, mais add prend deux nombres :
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 callRustc vous dit que la fonction devrait prendre un seul tuple, alors que add prend deux arguments distincts. Vous pouvez enrober la fonction dans une petite closure qui décompose le tuple puis l’appelle, ou écrire une version qui prend directement le tuple :
fn add(a: i32, b: i32) -> i32 { a + b}
// une version qui prend la paire comme un seul tuplefn add_pair((a, b): (i32, i32)) -> i32 { a + b}
fn main() { let pairs = vec![(1, 2), (3, 4), (10, 20)];
// enrober : un tuple en entrée, deux arguments en sortie let sums: Vec<i32> = pairs.iter().map(|&(a, b)| add(a, b)).collect(); println!("{:?}", sums);
// ou passer une fonction qui prend déjà un seul tuple let sums2: Vec<i32> = pairs.into_iter().map(add_pair).collect(); println!("{:?}", sums2);}Sortie :
[3, 7, 30][3, 7, 30]Le & dans |&(a, b)| est là parce que .iter() prête chaque paire sous la forme &(i32, i32), et le motif en copie les deux nombres. La closure d’enrobage est en général le meilleur choix : add reste une fonction normale à deux arguments, que le reste de votre code peut appeler.
Si votre propre fonction générique attend Fn(i32) -> i32 et que vous lui passez add à deux arguments, vous obtenez « function is expected to take 1 argument, but it takes 2 arguments ». La correction est une closure d’enrobage qui fournit la valeur manquante, comme dans la solution 3 de E0057.
Où vous le verrez souvent
Section intitulée « Où vous le verrez souvent »HashMap::retain (dans l’autre sens). La plupart des méthodes des maps vous donnent un tuple, mais retain passe la clé et la valeur comme deux arguments séparés. stock.retain(|(name, count)| *count > 0) donne donc « closure is expected to take 2 distinct arguments, but it takes a single 2-tuple as argument ». Correction : retirez les parenthèses, stock.retain(|_name, count| *count > 0). Vec::retain, lui, prend un seul élément, comme d’habitude.
Oublier le paramètre. tasks.iter().for_each(|| println!("task")) donne « closure is expected to take 1 argument, but it takes 0 arguments », et rustc suggère |_|. Correction : |_|, ou |task| si vous voulez vous en servir.
Les threads. thread::spawn(|id| ...) donne « closure is expected to take 0 arguments, but it takes 1 argument ». Correction : thread::spawn(move || ...), en capturant id depuis l’extérieur.
Un paramètre pointeur de fonction. Si votre fonction prend f: fn(i32) -> i32 (un simple pointeur de fonction, pas un générique F: Fn), passer add à deux arguments donne plutôt E0308, avec l’étiquette « incorrect number of function parameters » (nombre incorrect de paramètres). Même correction : une closure d’enrobage comme |x| add(x, 10). Les closures qui ne capturent rien peuvent être passées comme pointeurs fn.
Erreurs liées
Section intitulée « Erreurs liées »- E0057 : closure takes N arguments but M were supplied : vous appelez vous-même une closure avec le mauvais nombre de valeurs.
- E0631 : type mismatch in closure arguments : le nombre est bon, mais un paramètre a le mauvais type, souvent
i32là où l’itérateur donne&i32. - E0061 : function takes N arguments but M were supplied : vous appelez une fonction normale avec le mauvais nombre d’arguments.
- E0308 : mismatched types : ce que vous obtenez avec un paramètre pointeur
fn(...), ou avec|(a, b)|quand les éléments ne sont pas du tout des tuples. - E0599 : no method found : apparaît parfois juste après E0593, par exemple « the method
collectexists … but its trait bounds were not satisfied ». Corrigez d’abord E0593 et elle disparaît. - Toutes les erreurs du compilateur : la liste complète des pages d’erreurs.
Petit quiz
Section intitulée « Petit quiz »Petit quiz
1. names.iter().enumerate().map(|i, name| ...) donne E0593. Quelle est la correction ?
2. Combien de paramètres faut-il à la closure de scores.sort_by(...) ?
3. thread::spawn(|id| println!("{id}")) donne E0593. Comment faire entrer id dans le thread ?
Score : 0 / 3
Pour aller plus loin
Section intitulée « Pour aller plus loin »- E0593 — Rust error codes index (en anglais) : l’explication officielle de cette erreur.
- Closures — The Rust Book (en anglais) : la syntaxe des closures, l’inférence du type des paramètres et les traits
Fn. - As input parameters — Rust by Example (en anglais) : comment la contrainte
Fnd’une fonction décide quelle closure elle accepte. Iterator::enumerate— documentation de la bibliothèque standard (en anglais) : montre que chaque élément devient un seul tuple(index, valeur).HashMap::retain— documentation de la bibliothèque standard (en anglais) : la méthode des maps dont la closure prend la clé et la valeur comme deux arguments.