Erreur Rust E0631 : type mismatch in closure arguments (types incompatibles)
E0631 veut dire que votre closure a le bon nombre de paramètres, mais qu’au moins l’un d’eux a le mauvais type. Neuf fois sur dix, la différence tient en un seul & : vous avez écrit |x: i32|, mais l’itérateur vous prête un &i32. La correction la plus rapide consiste souvent à supprimer l’annotation de type et à laisser Rust trouver tout seul. Cette page vous montre d’abord cette solution, puis vous explique d’où vient ce &, pour que vous puissiez le prévoir la prochaine fois. 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[E0631]: type mismatch in closure arguments --> src/main.rs:3:43 |3 | let doubled: Vec<i32> = prices.iter().map(|p: i32| p * 2).collect(); // ❌ items are &i32 | ^^^ -------- found signature defined here | | | expected due to this | = note: expected closure signature `fn(&{integer}) -> _` found closure signature `fn(i32) -> _`note: required by a bound in `map`help: consider adjusting the signature so it borrows its argument |3 | let doubled: Vec<i32> = prices.iter().map(|p: &i32| p * 2).collect(); // ❌ items are &i32 | +Le compilateur vous dit : « types incompatibles dans les arguments de la closure ». L’aide (help:) vous propose de modifier la signature pour que la closure emprunte son argument.
Voici le programme qui la provoque :
fn main() { let prices = vec![120, 45, 300]; let doubled: Vec<i32> = prices.iter().map(|p: i32| p * 2).collect(); // ❌ items are &i32 println!("{:?}", doubled);}(Le commentaire dit : les éléments sont des &i32.)
Juste après, rustc affiche aussi une deuxième erreur, plus longue : the method `collect` exists for struct `Map<...>`, but its trait bounds were not satisfied (E0599). Ce n’est qu’un effet secondaire. Corrigez E0631 et elle disparaît.
Ce que ça veut dire
Section intitulée « Ce que ça veut dire »Les deux lignes note sont le cœur du message. Lisez-les comme « la méthode voulait ceci, vous avez écrit cela » :
- expected
fn(&{integer}) -> _(attendu) :mapva appeler votre closure avec une référence vers un entier.{integer}veut dire « un type entier que Rust n’a pas encore fixé », et_veut dire « n’importe quel type de retour ». - found
fn(i32) -> _(trouvé) : votre closure dit qu’elle prend uni32par valeur.
Mettez-les côte à côte : la seule différence, c’est le &. La ligne help: montre la correction d’un seul caractère.
Pensez au vestiaire d’un théâtre. Vous déposez votre manteau et on vous donne un ticket. .iter() fonctionne de la même façon : il ne vous donne pas les éléments, il vous donne des tickets qui y mènent (des références). Une closure qui exige le manteau lui-même n’acceptera pas un ticket.
Pourquoi Rust fait ça
Section intitulée « Pourquoi Rust fait ça »Chaque méthode d’itérateur décide exactement quel type elle passe à votre closure, et ce type dépend de la façon dont vous avez créé l’itérateur :
v.iter()prête chaque élément :&T.v.into_iter()(sur unVecqui vous appartient) donne chaque élément :T.filteretfindvous prêtent l’élément encore une fois, donc par-dessus.iter()vous obtenez&&T.sort_byvous prête deux éléments :&Tet&T.
Quand vous n’écrivez pas le type des paramètres, Rust le déduit de la méthode, donc il correspond toujours. Quand vous écrivez un type vous-même, Rust vous prend au mot et le vérifie. Il ne va pas copier la valeur en douce ni transformer une référence en valeur possédée, car cela changerait ce que fait votre code (et pour une String, une copie coûte de la mémoire et du temps). La leçon Les itérateurs montre les trois façons d’obtenir un itérateur, et Le borrowing (l’emprunt) explique les références.
Comment corriger
Section intitulée « Comment corriger »Solution 1 : retirer l’annotation de type
Section intitulée « Solution 1 : retirer l’annotation de type »Essayez-la en premier. Les paramètres de closure n’ont presque jamais besoin d’un type : Rust sait exactement ce que passent map, filter et sort_by. Supprimez : i32 et le code compile :
fn main() { let prices = vec![120, 45, 300]; let ages = vec![12, 34, 17, 50]; let mut scores = vec![42, 7, 99, 15];
// pas d'annotation de type : Rust utilise ce que l'itérateur donne vraiment let doubled: Vec<i32> = prices.iter().map(|p| p * 2).collect(); let adults: Vec<&i32> = ages.iter().filter(|age| **age >= 18).collect(); scores.sort_by(|a, b| b.cmp(a));
println!("{:?}", doubled); println!("{:?}", adults); println!("{:?}", scores);}Sortie :
[240, 90, 600][34, 50][99, 42, 15, 7]p * 2 fonctionne sur un &i32, car les opérateurs numériques de Rust acceptent aussi les références. Dans filter, age est un &&i32, donc **age suit les deux références pour atteindre le nombre. Les comparaisons comme b.cmp(a) marchent directement sur des références.
Solution 2 : écrire le type que la méthode passe vraiment
Section intitulée « Solution 2 : écrire le type que la méthode passe vraiment »Choisissez-la quand vous voulez garder le type visible, par exemple pour rendre une longue chaîne d’appels plus lisible. Suivez le help: de rustc et ajoutez le &. Vous pouvez aussi mettre le & dans le motif : |&p| décompose la référence et vous donne une simple copie du nombre (cela marche pour les types Copy comme les entiers) :
fn main() { let prices = vec![120, 45, 300]; let ages = vec![12, 34, 17, 50]; let mut scores = vec![42, 7, 99, 15];
// écrire les vrais types : & pour .iter(), && dans filter let doubled: Vec<i32> = prices.iter().map(|p: &i32| p * 2).collect(); let adults: Vec<&i32> = ages.iter().filter(|age: &&i32| **age >= 18).collect(); scores.sort_by(|a: &i32, b: &i32| b.cmp(a));
// ou copier le nombre avec un motif & let tax: Vec<i32> = prices.iter().map(|&p| p / 10).collect();
println!("{:?}", doubled); println!("{:?}", adults); println!("{:?}", scores); println!("{:?}", tax);}Sortie :
[240, 90, 600][34, 50][99, 42, 15, 7][12, 4, 30]Notez la différence : |p: &i32| veut dire « p est une référence », alors que |&p| veut dire « je reçois une référence, donnez-moi ce qu’il y a derrière ».
Solution 3 : changer ce que donne l’itérateur
Section intitulée « Solution 3 : changer ce que donne l’itérateur »Choisissez-la quand vous voulez vraiment les valeurs, et pas des références. Il y a deux façons de les obtenir :
.iter().copied()transforme chaque&i32eni32. Utilisez-la pour les petits typesCopycomme les nombres,charetbool. (.cloned()fait la même chose pour des types commeString, en clonant.).into_iter()sur unVecdonne les éléments eux-mêmes. LeVecest consommé ensuite, alors choisissez-la quand vous n’en avez plus besoin.
fn main() { let prices = vec![120, 45, 300];
// .copied() transforme les éléments &i32 en i32 let doubled: Vec<i32> = prices.iter().copied().map(|p: i32| p * 2).collect(); println!("{:?}", doubled);
// .into_iter() sur un Vec donne les éléments eux-mêmes (et consomme le Vec) let names = vec![String::from("ferris"), String::from("corro")]; let loud: Vec<String> = names.into_iter().map(|name: String| name.to_uppercase()).collect(); println!("{:?}", loud);}Sortie :
[240, 90, 600]["FERRIS", "CORRO"]Si vous avez encore besoin de names après la boucle, gardez .iter() et prenez plutôt un &String. Sortir une valeur d’un Vec que vous avez seulement emprunté, c’est une autre erreur : E0507.
Solution 4 : faire prendre à une fonction nommée ce que donne l’itérateur
Section intitulée « Solution 4 : faire prendre à une fonction nommée ce que donne l’itérateur »Choisissez-la quand vous passez une fonction par son nom, comme .map(shout). Le message dit alors « function » au lieu de « closure » :
error[E0631]: type mismatch in function arguments --> src/main.rs:7:51 |1 | fn shout(word: &String) -> String { | --------------------------------- found signature defined here...7 | let loud: Vec<String> = words.into_iter().map(shout).collect(); | --- ^^^^^ expected due to this | | | required by a bound introduced by this call | = note: expected function signature `fn(&str) -> _` found function signature `fn(&String) -> _`Les éléments sont des &str, mais shout veut un &String. Une bonne habitude : faites prendre un &str aux fonctions qui ne font que lire du texte. Elles acceptent alors les littéraux de chaîne, les &String et plus encore. Quand l’itérateur ajoute un & de plus (comme le fait filter), enrobez l’appel dans une petite closure. Dans un appel de fonction normal, Rust retire les & en trop pour vous (c’est ce qu’on appelle la deref coercion), mais il ne peut pas le faire quand vous passez la fonction par son nom :
// prendre &str : marche pour les littéraux, les &String et plus encorefn shout(word: &str) -> String { word.to_uppercase()}
fn is_long(word: &str) -> bool { word.len() > 4}
fn main() { let words = vec!["crab", "rustacean", "ferris"];
// les éléments sont des &str, donc on peut passer shout par son nom let loud: Vec<String> = words.iter().copied().map(shout).collect(); println!("{:?}", loud);
// filter donne des &&str : une petite closure laisse Rust convertir pour vous let long: Vec<&str> = words.iter().copied().filter(|w| is_long(w)).collect(); println!("{:?}", long);}Sortie :
["CRAB", "RUSTACEAN", "FERRIS"]["rustacean", "ferris"]Les mots restent en anglais ici, car is_long compte des octets avec .len(), et les lettres accentuées en prennent deux. Pour en savoir plus sur &str et String, voir Les chaînes de caractères.
Où vous le verrez souvent
Section intitulée « Où vous le verrez souvent »Trier avec des paramètres typés. scores.sort_by(|a: i32, b: i32| b.cmp(&a)) donne E0631 avec expected closure signature `for<'a, 'b> fn(&'a {integer}, &'b {integer}) -> _`. La partie for<'a, 'b> dit seulement « pour n’importe quel emprunt ». Correction : |a, b| b.cmp(a). Voir Le tri.
Les entrées d’une map avec des types possédés. stock.iter().map(|(name, count): (String, i32)| ...) sur une HashMap<String, i32> donne expected closure signature `fn((&String, &_)) -> _`. Sur une map, .iter() prête à la fois la clé et la valeur. Correction : retirez l’annotation, ou écrivez (&String, &i32).
Votre propre fonction générique. Si fn apply<F: Fn(i32) -> i32>(f: F, x: i32) reçoit une closure |x: f64| x / 2.0, rustc signale expected closure signature `fn(i32) -> _` et found closure signature `fn(f64) -> _`, en pointant vers la contrainte. Correction : faites prendre un i32 à la closure (et convertissez à l’intérieur, x as f64 / 2.0), ou changez la contrainte si c’est bien f64 que vous vouliez. Voir Écrire des fonctions qui prennent des closures.
for_each avec String. words.iter().for_each(|w: String| ...) sur un Vec<&str> attend fn(&&str). Un &str n’est pas une String. Correction : |w|, ou |w: &&str|.
Erreurs liées
Section intitulée « Erreurs liées »- E0593 : closure takes the wrong number of arguments : l’erreur sœur, pour le nombre de paramètres (par exemple
|a, b|alors qu’un seul tuple est passé). - E0057 : closure called with the wrong number of arguments : vous appelez vous-même une closure en lui passant trop peu ou trop de valeurs.
- E0308 : mismatched types : l’erreur générale du « mauvais type », par exemple quand la valeur renvoyée par la closure a le mauvais type, ou pour un paramètre pointeur
fn(...). - E0599 : no method found : l’erreur « trait bounds were not satisfied » sur
collectousumqui suit E0631. Elle disparaît avec E0631. - E0507 : cannot move out of a borrowed value : quand vous essayez de prendre possession d’éléments que vous avez seulement empruntés avec
.iter(). - Toutes les erreurs du compilateur : la liste complète des pages d’erreurs.
Petit quiz
Section intitulée « Petit quiz »Petit quiz
1. let v = vec![1, 2, 3]; Quel type reçoit la closure dans v.iter().map(|x| ...) ?
2. La note dit expected closure signature fn(&{integer}) -> _, found closure signature fn(i32) -> _. Quelle est la correction la plus rapide ?
3. Que fait |&p| p * 2 dans prices.iter().map(...) ?
Score : 0 / 3
Pour aller plus loin
Section intitulée « Pour aller plus loin »- E0631 — Rust error codes index (en anglais) : l’explication officielle de cette erreur.
- Processing a Series of Items with Iterators — The Rust Book (en anglais) :
iter,into_iteretiter_mut, et ce que chacun donne. - Closures — The Rust Book (en anglais) : comment le type des paramètres d’une closure est déduit, et quand l’écrire vous-même.
Iterator::copied— documentation de la bibliothèque standard (en anglais) : transformer un itérateur de&Ten itérateur deT.slice::sort_by— documentation de la bibliothèque standard (en anglais) : la signature qui montre que la closure de comparaison reçoit deux références.