Aller au contenu

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.

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 others
note: 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>`.

Lisez le message de haut en bas :

  1. ^^^^^ sous total : c’est la variable dont Rust n’a pas pu décider le type.
  2. --- sous sum : « type must be known at this point » (le type doit être connu ici). sum peut renvoyer beaucoup de types différents, Rust a donc besoin de la réponse à cet endroit.
  3. La note : the type must implement `Sum<&i32>`. C’est la règle que Rust a trouvée : « total doit être un type qu’on peut obtenir en additionnant des valeurs &i32 ».
  4. 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ême Option et Result. 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 un impl qui 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.

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.

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

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

Le 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 arguments

Rustc 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 à Lisbonne
200 200 42
300 tient dans un u8 ? false
▶ Essayez par vous-même

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

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

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

  • 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

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