Erreur Rust E0282 : type annotations needed (annotation de type requise)
E0282 veut dire que Rust a essayé de trouver un type tout seul et qu’il n’a pas trouvé assez d’indices. Votre logique n’a rien de faux. Le compilateur a juste besoin que vous nommiez un type, en général avec quelques caractères de plus. Cette page vous montre d’abord la correction, puis explique comment Rust devine les types (on parle d’inférence de types), pour que vous sachiez quand lui donner un coup de main. 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[E0282]: type annotations needed for `Vec<_>` --> src/main.rs:2:9 |2 | let mut scores = Vec::new(); // un Vec de... quoi ? | ^^^^^^^^^^ ---------- type must be known at this point |help: consider giving `scores` an explicit type, where the type for type parameter `T` is specified |2 | let mut scores: Vec<T> = Vec::new(); // un Vec de... quoi ? | ++++++++Le compilateur dit : « annotations de type nécessaires pour Vec<_> ». Il propose de donner à scores un type explicite, en précisant ce que vaut T.
Voici le programme qui la provoque :
fn main() { let mut scores = Vec::new(); // un Vec de... quoi ? println!("Scores recorded: {}", scores.len());}Même message, autres codes
Section intitulée « Même message, autres codes »Les mots « type annotations needed » apparaissent aussi avec trois autres codes. Ils veulent dire la même chose, et les corrections de cette page marchent pour tous :
- E0283 :
.collect()ou.sum()sans type cible. rustc ajoute une note commethe type must implement `FromIterator<&str>`ou`Sum<&i32>`, parce que beaucoup de types pourraient être le résultat. - E0284 :
"42".parse().unwrap()sans type cible. La note ditcannot satisfy `<_ as FromStr>::Err == _`, une façon effrayante de dire « convertir en quoi ? ». - E0790 :
let settings = Default::default();. Le titre ditcannot call associated function on trait without specifying the corresponding `impl` type: Rust ne sait pas de quel type vous voulez la valeur par défaut.
Le code que vous obtenez dépend de la ligne exacte, et il peut changer si vous ajoutez du code. Par exemple, let age = "42".parse().unwrap(); donne E0284, mais si la ligne suivante appelle age.pow(2), ça devient E0282. Ne vous souciez pas du numéro : regardez la variable que montre rustc.
Ce que ça veut dire
Section intitulée « Ce que ça veut dire »Regardez les deux marques sous la ligne 2 :
^^^^^^^^^^sousmut scores: c’est la variable dont Rust n’a pas pu trouver le type.----------sousVec::new(): « type must be known at this point » (le type doit être connu à cet endroit).Vec::new()crée une liste vide, mais une liste vide de quoi ? De nombres ? De noms ? Rust doit le savoir pour réserver le bon genre de stockage.- L’aide propose
let mut scores: Vec<T>. Ici,Tn’est qu’un espace réservé. Remplacez-le par un vrai type, commeVec<u32>.
Pensez à un colis à la poste dont la case « contenu » est restée vide. Le guichetier ne peut pas l’envoyer tant que quelqu’un n’a rien écrit dedans. Vous pouvez l’écrire vous-même, ou mettre quelque chose dans le colis pour que le guichetier voie ce que c’est.
Pourquoi Rust fait ça
Section intitulée « Pourquoi Rust fait ça »En Rust, chaque valeur a un seul type exact, fixé à la compilation. La plupart du temps, vous ne l’écrivez pas : Rust lit votre code et le déduit. let count = 5; est un i32. let name = String::from("Ferris"); est une String. C’est l’inférence de types, et la leçon Les variables en montre d’autres exemples.
L’inférence utilise des indices venus de toute la fonction, pas seulement d’une ligne. Si vous appelez scores.push(90) plus loin, c’est un indice : le Vec contient des nombres. Si vous passez scores à une fonction qui prend un &[u32], c’est un indice encore meilleur. E0282 apparaît quand il n’y a aucun indice, ou quand Rust a besoin du type avant d’arriver à l’indice (par exemple, pour trouver une méthode).
Certaines fonctions sont génériques sur leur type de retour : parse, collect, sum, Default::default, Vec::new, None. Elles peuvent produire beaucoup de types différents, donc Rust ne peut pas en choisir un pour vous. Il refuse de deviner, parce qu’une mauvaise supposition changerait en silence le comportement de votre programme. La page Les génériques montre comment une même fonction peut renvoyer beaucoup de types.
Comment corriger
Section intitulée « Comment corriger »Solution 1 : annoter la variable
Section intitulée « Solution 1 : annoter la variable »C’est le bon choix dans la plupart des cas. Ajoutez : Type après le nom de la variable. C’est court, et ça documente aussi votre code pour la personne qui le lira ensuite :
fn main() { let scores: Vec<u32> = Vec::new(); let age: u32 = "42".parse().unwrap(); let words: Vec<&str> = "rouge vert bleu".split(' ').collect(); let winner: Option<String> = None;
println!("Scores enregistrés : {}", scores.len()); println!("L'an prochain, vous aurez {} ans", age + 1); println!("{:?}", words); println!("{:?}", winner);}Sortie :
Scores enregistrés : 0L'an prochain, vous aurez 43 ans["rouge", "vert", "bleu"]NoneVous n’êtes pas toujours obligé d’écrire le type complet. let words: Vec<_> = ...collect(); veut dire « un Vec, et c’est toi qui trouves ce qu’il y a dedans ». Le _ demande à Rust de compléter cette partie. Plus d’informations sur l’écriture des types dans Les types de données.
Solution 2 : utiliser le turbofish ::<>
Section intitulée « Solution 2 : utiliser le turbofish ::<> »Choisissez cette solution quand il n’y a pas de variable à annoter, par exemple au milieu d’une chaîne d’appels d’itérateurs. Vous mettez le type directement sur l’appel de fonction avec ::<Type>. On appelle ::<> le « turbofish » (le « turbo-poisson »), parce que ::<> ressemble un peu à un poisson :
fn main() { let input = "4 15 7"; let numbers = input .split(' ') .map(|s| s.parse::<u32>().unwrap()) .collect::<Vec<_>>(); let total = numbers.iter().sum::<u32>();
println!("{:?}", numbers); println!("Total : {total}");}Sortie :
[4, 15, 7]Total : 26Les trois turbofish les plus courants sont parse::<i32>(), collect::<Vec<_>>() et sum::<i32>(). Les solutions 1 et 2 sont aussi correctes l’une que l’autre ; let total: u32 = numbers.iter().sum(); fait le même travail. Prenez celle qui se lit le mieux.
Solution 3 : utiliser la valeur, pour que Rust puisse deviner le type
Section intitulée « Solution 3 : utiliser la valeur, pour que Rust puisse deviner le type »Choisissez cette solution quand l’erreur apparaît parce que votre code n’est pas encore terminé. Souvent, vous créez un Vec vide, et le code qui le remplit vient plus tard. Dès que vous utilisez la valeur, Rust a son indice et l’erreur disparaît, sans aucune annotation :
fn print_team(names: &[String]) { for name in names { println!("- {name}"); }}
fn main() { let mut team = Vec::new(); // type inconnu... pour l'instant team.push(String::from("Ferris")); // maintenant Rust sait : Vec<String> team.push(String::from("Corro")); print_team(&team);}Sortie :
- Ferris- CorroL’ordre compte un peu. L’indice doit arriver avant que Rust ait besoin du type pour appeler une méthode. Une closure comme let shout = |s| s.to_uppercase(); échoue quand même si vous appelez shout("hi") à la ligne suivante, parce que Rust doit savoir ce qu’est s pour trouver .to_uppercase(). Dans ce cas, écrivez le type : |s: &str| s.to_uppercase(). Voir Les closures.
Solution 4 : nommer le type pour None et Default::default()
Section intitulée « Solution 4 : nommer le type pour None et Default::default() »Choisissez cette solution pour les valeurs qui ne donnent aucun indice. None ne dit pas ce qu’il contiendrait, et Default::default() ne dit pas de quel type vous voulez la valeur par défaut. Écrivez le type dans l’expression :
#[derive(Debug, Default)]struct Settings { volume: u8, muted: bool,}
fn main() { let fastest_lap = None::<f64>; // au lieu de : None let settings = Settings::default(); // au lieu de : Default::default() let backup: Settings = Default::default(); // une annotation marche aussi
println!("{:?}", fastest_lap); println!("{:?}", settings); println!("Volume de secours : {}", backup.volume);}Sortie :
NoneSettings { volume: 0, muted: false }Volume de secours : 0Settings::default() est le choix le plus clair : il se lit « les Settings par défaut ». None::<f64> est pratique, mais let fastest_lap: Option<f64> = None; (solution 1) est plus courant. Voir Option.
Où vous le verrez souvent
Section intitulée « Où vous le verrez souvent »Convertir du texte en nombre. let guess = line.trim().parse().unwrap(); donne E0284 (ou E0282 si vous appelez une méthode sur guess juste après). parse peut produire un i32, un u8, un f64 et bien d’autres. Correction : let guess: i32 = ... ou .parse::<i32>(). Comparer guess à une variable i32 plus loin ne suffit pas ici, parce que parse a besoin de son type au moment même de l’appel.
.collect(). Il peut construire un Vec, une String, un HashSet, une HashMap… il a donc toujours besoin de connaître la cible. Correction : let words: Vec<_> = ...collect(); ou .collect::<Vec<_>>(). Voir Les itérateurs.
.sum() et .product(). nums.iter().sum() donne E0283 avec the type must implement `Sum<&i32>`. Additionner des i32 pourrait donner un i32, mais d’autres types de nombres peuvent aussi être des sommes. Correction : let total: i32 = nums.iter().sum(); ou .sum::<i32>().
Une collection vide dans le constructeur d’une struct. let cache = HashMap::new(); dans une fonction new() que vous n’avez pas fini d’écrire. Correction : écrivez une fois le type complet (HashMap<String, u32>), ou renvoyez-la dans une struct dont le type du champ est déjà déclaré, ce qui est un indice que Rust peut utiliser.
Erreurs liées
Section intitulée « Erreurs liées »- E0308 : mismatched types (types incompatibles). Elle arrive quand votre annotation et la valeur ne sont pas d’accord, comme
let age: u32 = "42";. - E0277 : trait bound not satisfied (trait manquant). Vous avez donné un type, mais il ne peut pas être construit de cette façon, comme
let n: Vec<i32> = "1 2".split(' ').collect();(c’est unVec<&str>; convertissez d’abord chaque élément). - E0599 : no method found (méthode introuvable), par exemple quand vous annotez
let n: u32puis appelez dessus une méthode deStringcomme.trim(). - E0004 : non-exhaustive patterns (motifs non exhaustifs), une autre erreur où rustc écrit la correction pour vous.
- Toutes les erreurs du compilateur : la liste complète des pages d’erreurs.
Petit quiz
Section intitulée « Petit quiz »Petit quiz
1. Quelle ligne corrige E0282 sur let ids = Vec::new(); quand le Vec doit contenir des valeurs u64 ?
2. Pourquoi let n = "7".parse().unwrap(); a-t-il besoin d'un type, alors que le texte est clairement un nombre ?
3. Que veut dire le _ dans .collect::<Vec<_>>() ?
Score : 0 / 3
Pour aller plus loin
Section intitulée « Pour aller plus loin »- E0282 — Rust error codes index (en anglais) : l’explication officielle de cette erreur, avec d’autres exemples.
- E0283 — Rust error codes index (en anglais) : la variante que vous verrez avec
collectetsum, quand beaucoup de types conviendraient. - Data Types — The Rust Book (en anglais) : explique les annotations, y compris l’exemple classique de
"42".parse(). - Inference — Rust by Example (en anglais) : un court exemple où Rust déduit le type d’un
Vecà partir du code qui suit. Iterator::collect— documentation de la bibliothèque standard (en anglais) : montre à la fois le style annotation et le style turbofish.