Aller au contenu

Erreur Rust E0284 : type annotations needed (type manquant pour parse())

E0284 veut presque toujours dire une seule chose : vous avez appelé .parse(), et Rust ne sait pas en quoi convertir le texte. La note cannot satisfy `<_ as FromStr>::Err == _` a l’air effrayante, mais la correction tient en général en quelques caractères, comme : u32 ou ::<u32>. Cette page vous donne d’abord la solution, puis décode cette note pour qu’elle cesse de vous faire peur. Pour les autres erreurs de cette série, consultez les erreurs du compilateur Rust.

error[E0284]: type annotations needed
--> src/main.rs:2:9
|
2 | let age = "42".parse().unwrap();
| ^^^ ----- type must be known at this point
|
= note: cannot satisfy `<_ as FromStr>::Err == _`
help: consider giving `age` an explicit type
|
2 | let age: /* Type */ = "42".parse().unwrap();
| ++++++++++++

Le compilateur vous dit : « annotations de type nécessaires ». L’étiquette sous parse signifie « le type doit être connu à cet endroit », et l’aide propose de donner un type explicite à age.

Voici le programme qui la provoque :

fn main() {
let age = "42".parse().unwrap();
println!("Next year you'll be {}", age + 1);
}

Vous verrez la même note avec .expect(...) au lieu de .unwrap(), avec match "42".parse() { ... }, et avec un .parse() à l’intérieur d’une closure. Quand le résultat de .parse() n’est pas rangé dans une variable, rustc pointe parse lui-même et suggère parse::</* Type */>().

Les marques sont la partie facile :

  1. ^^^ sous age : la variable dont le type est inconnu.
  2. ----- sous parse : « type must be known at this point » (le type doit être connu à cet endroit). C’est là que Rust s’est retrouvé bloqué.
  3. L’aide : « consider giving age an explicit type » (pensez à donner un type explicite à age). Remplacez /* Type */ par un vrai type, comme u32.

Maintenant, la note, morceau par morceau. Dans les messages du compilateur, _ veut dire « un type que je ne connais pas encore » :

Morceau En clair
<_ as FromStr> « le type vers lequel vous convertissez (je ne le connais pas encore) »
::Err « … et le type d’erreur qui va avec »
== _ « doit être égal à un autre type que je ne connais pas non plus »

La note entière dit donc : « Je n’arrive même pas à savoir quel genre d’erreur parse pourrait renvoyer, parce que je ne sais pas en quoi vous convertissez. »

Imaginez que vous tendez une lettre à un traducteur en lui disant « traduisez-la ». En anglais ? En japonais ? Tant que vous ne l’avez pas dit, il ne peut pas commencer, et il ne peut même pas vous dire quelles difficultés il risque de rencontrer.

Voici la signature de parse, un peu simplifiée :

fn parse<F: FromStr>(&self) -> Result<F, F::Err>

F est le type que vous voulez obtenir, et F::Err est son type d’erreur. Chaque type cible apporte le sien : convertir en i32 peut échouer avec une ParseIntError, en f64 avec une ParseFloatError, en bool avec une ParseBoolError. Rust a besoin de F pour savoir ce que parse renvoie, tout simplement.

Ce choix change aussi ce qui se passe à l’exécution. "42" se convertit sans problème en u8, en i32 ou en f64. Mais "300" échoue en u8 (trop grand), et "4.5" échoue en i32 (ce n’est pas un nombre entier). Rust ne devine pas, parce qu’une mauvaise supposition deviendrait un bug qui n’apparaîtrait qu’avec certaines entrées. La leçon Les conversions montre comment fonctionne FromStr, et comment l’ajouter à vos propres types.

La façon dont Rust devine les types tout seul est expliquée sur la page de E0282. La raison pour laquelle il existe trois codes différents (E0282, E0283, E0284) pour « type annotations needed » est expliquée sur la page de E0283. En bref : E0284 est la variante où la règle bloquée porte sur un type qui appartient au type inconnu (ici F::Err). Pour vous, la correction est la même : nommez le type.

Choisissez-la dans la plupart des cas. C’est le style qu’utilise le Rust Book, et il indique clairement ce que le texte doit contenir :

fn main() {
let age: u32 = "42".parse().unwrap();
let price: f64 = "4.99".parse().unwrap();
let port: u16 = "8080".parse().expect("le port doit être un nombre");
println!("L'an prochain, vous aurez {} ans", age + 1);
println!("Deux cafés : {:.2}", price * 2.0);
println!("En écoute sur le port {port}");
}

Sortie :

L'an prochain, vous aurez 43 ans
Deux cafés : 9.98
En écoute sur le port 8080
▶ Essayez par vous-même

Choisissez le type qui correspond au sens du texte : u16 pour un port, u8 pour un pourcentage, f64 pour un prix. Un type trop petit transforme une entrée valide en erreur : dans le doute, prenez un type plus large comme i64 ou u32. (Notez que Rust écrit les nombres à virgule avec un point : "4.99", pas "4,99".)

Choisissez-la quand il n’y a pas de variable à annoter : dans une closure, au milieu d’une chaîne d’itérateurs, ou dans une expression comme ... * 2 :

fn main() {
let line = "3 7 5";
let total = line
.split(' ')
.map(|s| s.parse::<u32>().unwrap())
.sum::<u32>();
let doubled = "21".parse::<i32>().unwrap() * 2;
println!("Total : {total}");
println!("Le double : {doubled}");
}

Sortie :

Total : 15
Le double : 42
▶ Essayez par vous-même

Mettez le turbofish sur parse lui-même. Dans une chaîne, annoter seulement le résultat final ne suffit souvent pas ; voyez le premier point de la section « Où vous le verrez souvent » plus bas.

Solution 3 : nommer le type et bien gérer l’erreur

Section intitulée « Solution 3 : nommer le type et bien gérer l’erreur »

Choisissez-la pour de vraies entrées (fichiers, arguments de la ligne de commande, formulaires web), où .unwrap() ferait planter votre programme sur un texte invalide. Nommez le type sur parse et faites un match sur le Result. Remarquez comme le type décide quelles entrées réussissent :

fn main() {
for input in ["42", "300", "abc"] {
match input.parse::<u8>() {
Ok(n) => println!("{input} : obtenu {n}"),
Err(e) => println!("{input} : {e}"),
}
}
let rate: Result<f64, _> = "0.25".parse();
println!("{:?}", rate);
}

Sortie :

42 : obtenu 42
300 : number too large to fit in target type
abc : invalid digit found in string
Ok(0.25)
▶ Essayez par vous-même

Les messages d’erreur viennent de la bibliothèque standard et restent en anglais : « number too large to fit in target type » veut dire « nombre trop grand pour le type cible » (300 ne tient pas dans un u8), et « invalid digit found in string » veut dire « chiffre invalide dans la chaîne ».

Dans Result<f64, _>, vous ne nommez que le type de la valeur. Le _ laisse Rust remplir le type d’erreur (ParseFloatError), c’est-à-dire exactement la partie que réclamait la note. Pour en savoir plus, voir Result.

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 convertie va dans un champ de struct ou un paramètre de fonction dont le type est connu. Rust lit toute la fonction, et cette utilisation plus loin répond à la question :

struct Config {
port: u16,
workers: u8,
}
fn greet(age: u32) {
println!("Bienvenue ! Vous avez {age} ans.");
}
fn main() {
let port = "8080".parse().unwrap(); // u16, d'après Config
let workers = "4".parse().unwrap(); // u8, d'après Config
let config = Config { port, workers };
println!("Port {} avec {} workers", config.port, config.workers);
let age = "42".parse().unwrap(); // u32, d'après greet
greet(age);
}

Sortie :

Port 8080 avec 4 workers
Bienvenue ! Vous avez 42 ans.
▶ Essayez par vous-même

Toutes les utilisations ne comptent pas, et c’est pour ça que c’est la solution 4. Les calculs comme age + 1 et les comparaisons comme age >= limit donnent encore E0284, même quand limit est un u32 : les opérateurs fonctionnent entre beaucoup de paires de types, ils ne désignent donc pas un seul type précis. Et appeler une méthode, comme age.pow(2), transforme l’erreur en E0282. Dans le doute, utilisez la solution 1.

Convertir dans un map, puis sum(). let total: u32 = line.split(' ').map(|s| s.parse().unwrap()).sum(); donne encore E0284, alors que total a un type. Un u32 peut être la somme de valeurs u32 ou de valeurs &u32 : l’annotation ne dit donc pas à Rust ce qu’est chaque élément. Correction : s.parse::<u32>(). (Collecter dans un Vec<i32> fonctionne, en revanche, parce qu’un Vec<i32> ne peut contenir que des i32.)

Collecter des résultats de parse dans un Result. let nums: Result<Vec<_>, _> = text.split(' ').map(|x| x.parse()).collect(); donne E0284 : le _ de Vec<_> laisse le type des éléments ouvert. Correction : Result<Vec<i32>, _>. Ce motif bien pratique s’arrête au premier élément invalide et vous donne son erreur.

Lire une saisie de l’utilisateur. let guess = line.trim().parse().unwrap(); dans un jeu de devinettes ou un outil en ligne de commande. Correction : let guess: u32 = ..., exactement comme dans le Rust Book.

Avec ?, l’erreur est plus étrange. let port = text.parse()?; ne donne pas E0284. Dans l’édition 2021, Rust se rabat sur le type () et signale E0277 : the trait bound `(): FromStr` is not satisfied (la contrainte de trait (): FromStr n’est pas satisfaite ; l’édition 2024 écrit ! au lieu de ()). Même correction : let port: u16 = text.parse()?;.

  • E0282 : type annotations needed (annotation de type requise), quand il n’y a aucun indice (ou que le type est nécessaire pour un appel de méthode comme age.pow(2)).
  • E0283 : type annotations needed après .sum(), .collect() ou .into(), quand beaucoup de types conviennent. Cette page explique aussi pourquoi il y a trois codes différents.
  • E0790 : FromStr::from_str("42") ou Default::default() appelés sans préciser le type.
  • E0277 : ce que donne .parse()? sans type, et ce que vous obtenez quand le type choisi ne peut pas être construit à partir de texte.
  • E0599 : méthode introuvable, par exemple quand vous appelez .parse() sur quelque chose qui n’est pas du texte, comme une variable u32 (convertissez-la plutôt avec u8::try_from(n)).
  • Toutes les erreurs du compilateur : la liste complète des pages d’erreurs.

Petit quiz

1. Que veut dire cannot satisfy <_ as FromStr>::Err == _, en clair ?

2. Quelle ligne corrige let port = "8080".parse().unwrap(); ?

3. let total: u32 = line.split(' ').map(|s| s.parse().unwrap()).sum(); donne encore E0284. Où faut-il mettre le type ?

Score : 0 / 3