Erreur Rust E0283 : type annotations needed (sum, collect et into)
E0283 veut dire que Rust sait ce que votre valeur doit savoir faire, mais que beaucoup de types en sont capables, et que Rust refuse d’en choisir un à votre place. Vous la rencontrerez surtout juste après .sum(), .collect() ou .into(). C’est l’une des erreurs les plus faciles à corriger : vous nommez le type que vous voulez, et c’est réglé. 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[E0283]: type annotations needed --> src/main.rs:3:9 |3 | let total = prices.iter().sum(); | ^^^^^ --- type must be known at this point | = note: the type must implement `Sum<&i32>` = help: the following types implement trait `Sum<A>`: `Duration` implements `Sum<&'a Duration>` `Duration` implements `Sum` `Option<T>` implements `Sum<Option<U>>` `Result<T, E>` implements `Sum<Result<U, E>>` `Saturating<u128>` implements `Sum<&'a Saturating<u128>>` `Saturating<u128>` implements `Sum` `Saturating<u16>` implements `Sum<&'a Saturating<u16>>` `Saturating<u16>` implements `Sum` and 88 othersnote: required by a bound in `std::iter::Iterator::sum`help: consider giving `total` an explicit type |3 | let total: /* Type */ = prices.iter().sum(); | ++++++++++++Le compilateur vous dit : « annotations de type nécessaires ». Le type doit être connu à cet endroit, il doit implémenter Sum<&i32>, et l’aide (help:) vous propose de donner un type explicite à total.
Voici le programme qui la provoque :
fn main() { let prices = vec![250, 120, 99]; // prices in cents let total = prices.iter().sum(); println!("Total: {total} cents");}(Le commentaire dit : prix en centimes.)
.collect() et .into() donnent la même erreur, avec une autre note. Pour let words = text.split(' ').collect();, la note est the type must implement `FromIterator<&str>`. Pour let name = "Ferris".into();, c’est the type must implement `From<&str>`.
Ce que ça veut dire
Section intitulée « Ce que ça veut dire »Lisez le message de haut en bas :
^^^^^soustotal: c’est la variable dont Rust n’a pas pu décider le type.---soussum: « type must be known at this point » (le type doit être connu ici).sumpeut renvoyer beaucoup de types différents, Rust a donc besoin de la réponse à cet endroit.- La note :
the type must implement `Sum<&i32>`. C’est la règle que Rust a trouvée : «totaldoit être un type qu’on peut obtenir en additionnant des valeurs&i32». - La liste de l’aide : les types qui savent être « une somme de quelque chose ». Il y en a beaucoup : tous les types numériques,
Duration, et mêmeOptionetResult. Rust en montre huit et ajoute « and 88 others » (et 88 autres). C’est ce qu’on veut dire par multiple impls satisfy this bound : plus d’un type a unimplqui respecte la règle.
Imaginez que vous demandez « quelque chose à boire » dans un café. C’est une vraie règle (ça doit se boire), mais le thé, le jus de fruits et l’eau conviennent tous. Le serveur ne peut rien apporter tant que vous n’avez pas choisi. E0283, c’est Rust qui tient le plateau et attend votre réponse.
Pourquoi Rust fait ça
Section intitulée « Pourquoi Rust fait ça »sum, collect et into sont génériques sur leur type de retour. C’est le type que vous demandez qui décide de ce que fait la fonction. Le trait (Sum, FromIterator, From) n’est que la règle. Alors, quand rien n’indique le type à Rust, il a une règle mais pas de réponse unique.
Et la réponse compte. Collecter les mêmes mots dans un Vec<&str> garde les doublons, alors que les collecter dans un HashSet<&str> les supprime. Une somme dans un u8 déborde à 255, pas dans un u64. Si Rust choisissait un type pour vous, une toute petite modification ailleurs pourrait changer en silence le comportement de votre programme. Rust préfère vous demander une fois.
La façon dont Rust trouve les types tout seul (l’inférence de type) est expliquée sur la page E0282, on ne la répète donc pas ici. Pour les traits derrière ces méthodes, voir Les traits et Les génériques.
E0282, E0283, E0284 : un seul problème, trois codes
Section intitulée « E0282, E0283, E0284 : un seul problème, trois codes »Les trois disent « type annotations needed », et pour vous elles veulent dire la même chose : écrivez le type. Le code vous indique seulement ce que Rust était en train de faire quand il s’est retrouvé bloqué :
- E0282 : Rust n’a aucun indice sur le type, ou il avait besoin du type avant d’arriver à l’indice (par exemple pour trouver une méthode comme
.len()). - E0283 (cette page) : Rust a un indice sous la forme d’une règle de trait (
must implement `Sum<&i32>`), mais beaucoup de types respectent cette règle. - E0284 : la règle porte sur un type qui appartient au type inconnu, comme le type d’erreur de
parse(cannot satisfy `<_ as FromStr>::Err == _`). Rust ne peut pas la vérifier tant qu’il ne connaît pas le type principal.
Le code peut même changer pendant que vous tapez. let words = text.split(' ').collect(); suivi de println!("{:?}", words) donne E0283. Changez la ligne suivante en words.len() et ça devient E0282, parce que Rust a maintenant besoin du type pour trouver la méthode. Les solutions ci-dessous marchent pour les trois.
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 la plupart du temps. Ajoutez : Type après le nom de la variable. C’est court, et ça dit exactement à la personne qui lira votre code ce que vous construisez :
use std::collections::HashSet;
fn main() { let prices = vec![250, 120, 99]; let total: u32 = prices.iter().sum();
let tags = "rust,web,rust,cli"; let list: Vec<&str> = tags.split(',').collect(); let unique: HashSet<&str> = tags.split(',').collect();
println!("Total : {total} centimes"); println!("{:?}", list); println!("Tags uniques : {}", unique.len());}Sortie :
Total : 469 centimes["rust", "web", "rust", "cli"]Tags uniques : 3Le même collect(), deux résultats différents : le type que vous écrivez est vraiment l’instruction. Vous pouvez laisser Rust trouver l’intérieur avec _, comme dans let list: Vec<_> = ....
Solution 2 : utiliser le turbofish sur sum et collect
Section intitulée « Solution 2 : utiliser le turbofish sur sum et collect »Choisissez-la quand il n’y a pas de variable à annoter : dans un println!, au milieu d’une chaîne d’itérateurs, ou comme argument de fonction. Mettez le type sur l’appel avec ::<Type> (le « turbofish ») :
fn main() { let prices = vec![250, 120, 99]; println!("Total : {} centimes", prices.iter().sum::<u32>());
let line = "tea,cake,soup"; let items = line.split(',').collect::<Vec<_>>(); let initials = line.split(',').map(|s| &s[..1]).collect::<String>();
println!("{:?}", items); println!("Initiales : {initials}");}Sortie :
Total : 469 centimes["tea", "cake", "soup"]Initiales : tcsLe menu (« tea, cake, soup » : thé, gâteau, soupe) reste en anglais, car &s[..1] découpe le premier octet, et une lettre accentuée en occupe deux. .product::<u64>() fonctionne de la même façon. Les solutions 1 et 2 sont aussi correctes l’une que l’autre : prenez celle qui se lit le mieux.
Solution 3 : pour .into(), nommer le type cible (le turbofish ne marche pas)
Section intitulée « Solution 3 : pour .into(), nommer le type cible (le turbofish ne marche pas) »Choisissez-la pour .into() et .try_into(). On est tenté d’écrire "Ferris".into::<String>(), mais c’est une autre erreur, car le paramètre de type appartient au trait Into, pas à la méthode :
error[E0107]: method takes 0 generic arguments but 1 generic argument was supplied --> src/main.rs:2:25 |2 | let name = "Ferris".into::<String>(); | ^^^^ expected 0 generic argumentsRustc vous dit que la méthode prend 0 argument générique, alors que vous lui en avez fourni 1. Annotez plutôt la variable, ou appelez directement From sur le type cible, ce qui se lit bien aussi :
fn main() { let name: String = "Ferris".into(); let city = String::from("Lisbonne");
let small: u8 = 200; let big: u64 = small.into(); let also_big = u64::from(small);
let level: i64 = 42; let byte: u8 = level.try_into().unwrap(); let too_big = u8::try_from(300_i64);
println!("{name} habite à {city}"); println!("{big} {also_big} {byte}"); println!("300 tient dans un u8 ? {}", too_big.is_ok());}Sortie :
Ferris habite à Lisbonne200 200 42300 tient dans un u8 ? falseString::from(...) et u64::from(...) annoncent le type cible dès le début de la ligne, c’est pourquoi beaucoup de gens les préfèrent à .into() quand il n’y a aucun autre indice. Voir Les conversions.
Solution 4 : utiliser la valeur là où son type est fixé
Section intitulée « Solution 4 : utiliser la valeur là où son type est fixé »Choisissez-la quand la valeur va de toute façon quelque part où le type est connu, comme un paramètre de fonction ou un champ de struct. Rust lit la fonction en entier, donc cette utilisation plus loin suffit :
fn print_receipt(total: u32, items: Vec<&str>) { println!("{} articles, {} centimes", items.len(), total);}
fn main() { let prices = vec![250, 120, 99]; let total = prices.iter().sum(); // u32, grâce à print_receipt let items = "tea,cake,soup".split(',').collect(); // Vec<&str>, pour la même raison print_receipt(total, items);}Sortie :
3 articles, 469 centimesToutes les utilisations ne comptent pas. if prices.iter().sum() > 400 donne toujours E0283, car > peut comparer beaucoup de paires de types. Passer la valeur à un paramètre générique comme impl Display n’aide pas non plus, puisque beaucoup de types sont Display. L’utilisation doit nommer un seul type précis.
Où vous le verrez souvent
Section intitulée « Où vous le verrez souvent ».collect() directement dans println!. println!("{:?}", line.split(',').collect()); donne E0283, et rustc suggère lui-même la correction : .collect::<Vec<_>>().
.try_into().unwrap(). let small = big.try_into().unwrap(); donne E0283 avec the type must implement `TryFrom<i64>`. Correction : let small: u8 = big.try_into().unwrap(); ou u8::try_from(big).unwrap().
Default::default() passé à une fonction générique. Avec fn show(value: impl Debug), l’appel show(Default::default()) donne E0283 : Rust ne sait pas de quel type vous voulez la valeur par défaut. Correction : show(Settings::default()) ou show(u32::default()). Seul sur sa ligne, let s = Default::default(); donne plutôt E0790 ; voir Default.
Votre propre fonction générique. Si un paramètre de type n’apparaît que dans le type de retour, comme fn make<T: Default>() -> T, alors let v = make(); donne E0283. Correction : make::<Settings>() ou let v: Settings = make();.
Erreurs liées
Section intitulée « Erreurs liées »- E0282 : type annotations needed, quand Rust n’a aucun indice sur le type (ou en a besoin pour un appel de méthode).
- E0284 : type annotations needed avec
cannot satisfy `<_ as FromStr>::Err == _`, presque toujours à cause de.parse(). - E0790 :
Default::default()(ou une autre fonction de trait) appelée sans dire de quel type il s’agit. - E0277 : vous avez nommé un type, mais il ne peut pas être construit de cette façon, comme
let n: Vec<i32> = "1 2".split(' ').collect();. - E0107 : mauvais nombre d’arguments génériques, ce que vous donne
.into::<String>(). - Toutes les erreurs du compilateur : la liste complète des pages d’erreurs.
Petit quiz
Section intitulée « Petit quiz »Petit quiz
1. Que vous dit la note de E0283 the type must implement Sum<&i32> ?
2. Quelle ligne corrige let name = "Ferris".into(); ?
3. Pourquoi collecter les mêmes mots donne-t-il des résultats différents dans un Vec<&str> et dans un HashSet<&str> ?
Score : 0 / 3
Pour aller plus loin
Section intitulée « Pour aller plus loin »- E0283 — Rust error codes index (en anglais) : l’explication officielle, avec l’exemple classique : les
chars()inversés doivent-ils devenir unVec<char>ou uneString? - Processing a Series of Items with Iterators — The Rust Book (en anglais) : comment
sumetcollectconsomment un itérateur, avec des exemples annotés. Iterator::collect— documentation de la bibliothèque standard (en anglais) : montre côte à côte le style avec annotation et le style turbofish.Sum— documentation de la bibliothèque standard (en anglais) : le trait derrière.sum(), et la liste complète des types qui l’implémentent.Into— documentation de la bibliothèque standard (en anglais) : pourquoi on implémenteFromet on appelle.into(), et comment le type cible est choisi.