Les erreurs du compilateur Rust les plus couramment rencontrées dans RustRover – 2ème Partie

Read this post in other languages:

Les données d’utilisation de RustRover nous ont permis d’identifier les 10 erreurs du compilateur Rust les plus fréquentes, que nous avons commencé à examiner dans un premier article. Dans ce second article, nous poursuivons avec l’examen du top 5 de ces erreurs et abordons les aspects du langage Rust qui posent le plus de problèmes aux développeurs avec le compilateur.

Erreur fréquente n° 5 : E0433 (Utilisation d’une crate, d’un module ou d’un type non déclaré)

Cette erreur est similaire à l’erreur E0432 (Une importation n’a pas été résolue), que nous avons vue dans l’article précédent. La seule différence ici est qu’un chemin avec un composant problématique est utilisé directement dans un nom, sans la clause use. 17,5 % des utilisateurs de RustRover ont rencontré cette erreur. Dans l’exemple de code ci-dessous, le compilateur ne comprend pas s’il s’agit d’une crate ou d’un module et nous le fait savoir :

Cet exemple montre pourquoi les messages d’erreur de Rust sont autant appréciés. Même sans lire le message, le correctif à apporter est évident : vérifier les noms, ajouter les dépendances requises à Cargo.toml si nécessaire ou introduire les importations appropriées.

De son côté, RustRover permet de choisir les noms importés. 

L’utilisation de la saisie semi-automatique de code durant la saisie permet d’éviter cette erreur. 

Erreur fréquente n° 4 : E0425 (Un nom non résolu a été utilisé)

Poursuivons avec une autre erreur de la catégorie « non résolu ». 20,5 % des utilisateurs de RustRover ont déjà été dans une situation dans laquelle ils ont utilisé un nom non résolu, qui n’était syntaxiquement ni une crate ni un module. Voici ce que l’ont peut voir lorsque cette erreur survient :

Évidemment, lorsque cela arrive, le contexte n’est pas toujours aussi simple que dans cet exemple.

Hélas, même l’excellent compilateur de Rust n’est pas d’une grande aide face à cette erreur. Plusieurs approches sont possibles ici : réviser le nom, fournir une définition ou introduire des importations correctes.

Erreur fréquente n° 3 : E0599 (Une méthode est utilisée sur un type qui ne l’implémente pas)

Au risque de gâcher la surprise, à partir de maintenant, toutes les erreurs porteront sur la vérification de type ! En effet, les trois erreurs les plus fréquentes sont toutes liées à une utilisation de type incorrecte. Concernant l’erreur #3, 27,5 % des utilisateurs de RustRover ont tenté d’appeler une méthode sur une valeur de type qui n’implémente pas cette méthode. Examinons l’exemple suivant :

fn main() {
   let numbers: Vec = vec![4, 11, 15, 12];
   let sum: usize = numbers.sum();
   println!("Answer = {sum}");
}

Tout semble correct, sauf qu’il n’y a pas de méthode sum pour Vec. Voici le contenu exact du message d’erreur :

error[E0599]: `Vec` is not an iterator

Dans ce cas, il faut appeler une méthode iter en premier, puis utiliser sum sur l’itérateur résultant. 

RustRover dispose d’une fonctionnalité qui permet d’éviter ce genre d’erreur : la saisie semi-automatique chaînée. Voici un exemple de ce que cela donne :

Lorsque la suggestion de saisie semi-automatique est acceptée, les méthodes iter et sum sont ajoutées à la chaîne d’appels, ce qui corrige immédiatement le code.

En général, la saisie semi-automatique évite de recevoir un message d’erreur E0599. Si vous ne voyez pas de suggestion pour appeler la méthode dont vous avez besoin, il est possible que le problème réel provienne en réalité du fait que le type de l’appelé est incorrect.

Un autre aspect de cette méthode vaut la peine d’être mentionné : dans de nombreux contextes, vous avez le contrôle sur vos propres déclarations de type. Vous pouvez notamment appeler une méthode en premier et demander ensuite à RustRover de procéder au scaffolding de son implémentation en appuyant sur Alt-Entrée (⌥OptionRetour) et en exécutant l’action suggérée :

Suite à cette action, RustRover génère le modèle d’implémentation de la méthode en prenant en compte les informations contextuelles concernant les types :

impl Info {
   pub(crate) fn summary(&self) -> String {
       todo!()
   }
}

L’exécution de ce code résulte en une erreur d’exécution, mais au moins le compilateur n’y trouve rien à redire.

Erreur fréquente n° 2 : E0308 (Le type attendu ne correspondait pas au type reçu)

Imaginez que vous utilisiez une expression de type A dans un contexte où une valeur de type B est attendue. Ici, la notion de « contexte » peut désigner plusieurs choses, comme un paramètre de fonction sur un site d’appel, une déclaration de variable ou une instruction/expression de contrôle du flux. 30 % des utilisateurs de RustRover ont déjà été dans cette situation.

D’un point de vue conceptuel, deux choix s’offrent à nous dans ce cas : modifier une valeur ou modifier le contexte. La modification d’une valeur peut impliquer le typage, le référencement/déréférencement, l’appel d’une méthode de transformation ou son remplacement par quelque chose d’autre. On ne peut pas toujours agir sur le contexte, mais il est parfois possible de modifier le type attendu pour qu’il corresponde au type reçu.

RustRover prend en charge les deux modes opératoires en fournissant plusieurs correctifs rapides, par exemple :

Notez la deuxième suggestion dans la liste : dans ce cas, RustRover a suggéré une transformation assez sophistiquée d’une valeur qui convient au compilateur. Suivre la première suggestion corrigerait le contexte, à savoir la définition de la fonction. 

Erreur fréquente n° 1 : E0277 (Vous avez essayé d’utiliser un type qui n’implémente pas de trait à un endroit où un trait est attendu)

Enfin, nous arrivons à la 1ère place du classement ! L’erreur du compilateur Rust la plus fréquemment rencontrée dans RustRover est E0277. 32 % des utilisateurs de RustRover ont fait l’expérience de cette situation cauchemardesque mêlant types et traits. L’explication officielle fait un bon travail en fournissant des exemples et des explications des correctifs possibles. Ici, nous allons observer le comportement de RustRover.

Voici ma fonctionnalité de RustRover préférée lorsqu’il s’agit de ce genre d’erreur : 

Le compilateur renvoie le message d’erreur suivant :

error[E0277]: `Info` doesn't implement `std::fmt::Display`

Bien sûr, l’implémentation du traitDisplay est un choix possible, et RustRover peut aider pour le scaffolding d’une implémentation. Mais dans ce cas, je préfère appliquer la première suggestion, qui comprend deux étapes simultanées :

  • Dérivation d’une implémentation pour le trait Debug.
  • Modifier la chaîne de format pour appeler le formatage du trait Debug.

Malheureusement, dans de nombreux autres cas, RustRover ne parvient pas à découvrir et à mettre en évidence cette erreur par lui-même. Comme mentionné dans l’article précédent, sa fonctionnalité de vérification de type n’est pas encore assez puissante, mais nous travaillons activement à son amélioration.

Observation des erreurs Rust en général

Récemment, nous avons fait un sondage auprès des membres de la communauté afin de savoir quelle est l’aspect du langage causant le plus d’erreurs dans leur code, et voici les réponses que nous avons obtenues :

Afin de comparer les réponses de la communauté à nos données, nous avons examiné les 25 erreurs de compilateur Rust les plus fréquentes et les avons classées en cinq grandes catégories :

  • Types et traits
  • Propriété et durées de vie
  • Macros
  • Noms non résolus ou éléments inexistants
  • Autre

Voici les résultats (numéros de code d’erreur classés par ordre croissant) :

Code de l’erreur Description Catégorie 
E0061 Un nombre non valide d’arguments a été passé lors de l’appel d’une fonction. Autre
E0063 Aucun champ de structure ou de variante d’énumération de type structure n’a été fourni. non résolu/non existant
E0106 Cette erreur indique que l’un des types ne comporte pas de durée de vie. propriété et durées de vie
E0107 Un nombre incorrect d’arguments génériques a été fourni. types et traits
E0277 Vous avez essayé d’utiliser un type qui n’implémente pas un trait à un endroit où un trait est attendu types et traits
E0282 Le compilateur n’a pas pu déduire un type et a demandé une annotation de type. types et traits
E0283 Une implémentation ne peut pas être choisie de façon claire en raison du manque d’informations. types et traits
E0308 Le type attendu ne correspond pas au type reçu. types et traits
E0369 Une opération binaire a été tentée sur un type qui ne la prend pas en charge. types et traits
E0382 Une variable a été utilisée après que son contenu ait été déplacé. propriété et durées de vie
E0412 Un nom de type utilisé n’est pas dans la portée. non résolu/non existant
E0423 Un identifiant a été utilisé comme un nom de fonction ou une valeur était attendue et l’identifiant existe mais appartient à un espace de nom différent. non résolu/non existant
E0425 Un nom non résolu a été utilisé. non résolu/non existant
E0432 Une importation n’a pas été résolue. non résolu/non existant
E0433 Une crate, un module ou un type non déclaré a été utilisé. non résolu/non existant
E0502 Une variable déjà empruntée comme immuable a été empruntée comme mutable. propriété et durées de vie
E0507 Une valeur empruntée a été déplacée. propriété et durées de vie
E0515 Une référence à une variable locale a été renvoyée. propriété et durées de vie
E0596 Cette erreur se produit parce que vous avez tenté d’emprunter de façon mutable une variable non mutable. propriété et durées de vie
E0597 Cette erreur se produit car une valeur a été supprimée alors qu’elle était encore empruntée. propriété et durées de vie
E0599 Cette erreur se produit lorsqu’une méthode est utilisée pour un type qui ne l’implémente pas. types et traits
E0609 Tentative d’accès à un champ non existant dans une structure. non résolu/non existant
E0614 Tentative de déférencement d’une variable qui ne peut pas être déférencée. Autre
E0658 Une fonctionnalité instable a été utilisée. Autre
E0716 Une valeur temporaire est supprimée alors qu’un emprunt est toujours actif. propriété et durées de vie

Malheureusement, les données dont nous disposons ne donnent pas beaucoup d’informations sur les macros. Nous ne pouvons ni détecter de façon fiable les problèmes d’expansion de macros, ni identifier d’autres erreurs résultant d’expansions de macros réussies. Cela montre que nous pourrions appliquer une catégorisation plus fine aux données que nous collectons. 

En faisant abstraction des macros, aucune des 25 erreurs les plus fréquentes ne se détache véritablement, il ne semble donc pas qu’il y ait un aspect de Rust en particulier qui pose davantage problème aux développeurs :

  • Types et traits – 7 erreurs
  • Propriété et durées de vie – 8 erreurs
  • Noms non résolus ou éléments inexistants – 7 erreurs
  • Autres – 3 erreurs

Résumé

Dans ce second article, nous avons passé en revue les cinq erreurs du compilateur Rust les plus fréquemment rencontrées dans RustRover et les correctifs possibles. Trois d’entre elles se rapportent aux types et traits, ce dont on peut déduire que ces élément ont un rôle significatif dans l’obtention d’un meilleur code Rust de meilleure qualité. En examinant des données plus générales relatives aux 25 erreurs les plus fréquentes, on voit aussi que bien d’autres aspects de Rust peuvent contribuer aux erreurs que nous rencontrons souvent.

Auteur de l’article original en anglais :

Delphine Massenhove

Vitaly Bragilevsky

image description