Aller au contenu

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.

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());
}

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 comme the 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 dit cannot satisfy `<_ as FromStr>::Err == _`, une façon effrayante de dire « convertir en quoi ? ».
  • E0790 : let settings = Default::default();. Le titre dit cannot 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.

Regardez les deux marques sous la ligne 2 :

  1. ^^^^^^^^^^ sous mut scores : c’est la variable dont Rust n’a pas pu trouver le type.
  2. ---------- sous Vec::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.
  3. L’aide propose let mut scores: Vec<T>. Ici, T n’est qu’un espace réservé. Remplacez-le par un vrai type, comme Vec<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.

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.

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 : 0
L'an prochain, vous aurez 43 ans
["rouge", "vert", "bleu"]
None
▶ Essayez par vous-même

Vous 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.

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 : 26
▶ Essayez par vous-même

Les 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
- Corro
▶ Essayez par vous-même

L’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 :

None
Settings { volume: 0, muted: false }
Volume de secours : 0
▶ Essayez par vous-même

Settings::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.

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.

  • 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 un Vec<&str> ; convertissez d’abord chaque élément).
  • E0599 : no method found (méthode introuvable), par exemple quand vous annotez let n: u32 puis appelez dessus une méthode de String comme .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

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