Erreur Rust E0428 : the name is defined multiple times (nom en double)
E0428 veut dire que vous avez défini deux choses avec le même nom au même endroit : deux fonctions appelées area, deux structs appelées User, ou un enum et une struct qui s’appellent tous les deux Status. Cela arrive souvent quand vous essayez de surcharger (overload) une fonction, comme vous le feriez en Java, en C# ou en C++. Rust n’a pas de surcharge, mais il a de bonnes alternatives, et cette page vous les montre. Elle fait partie du guide des erreurs du compilateur Rust.
Le message d’erreur
Section intitulée « Le message d’erreur »error[E0428]: the name `area` is defined multiple times --> src/main.rs:5:1 |1 | fn area(width: f64, height: f64) -> f64 { | --------------------------------------- previous definition of the value `area` here...5 | fn area(radius: f64) -> f64 { // ❌ un deuxième `area` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ `area` redefined here | = note: `area` must be defined only once in the value namespace of this moduleLe compilateur vous dit : « le nom area est défini plusieurs fois ». La section « Ce que ça veut dire » ci-dessous traduit chaque ligne.
Voici le programme qui la provoque :
fn area(width: f64, height: f64) -> f64 { width * height}
fn area(radius: f64) -> f64 { // ❌ un deuxième `area` 3.14159 * radius * radius}
fn main() { println!("{}", area(2.0, 3.0)); println!("{}", area(1.0));}rustc affiche aussi une deuxième erreur pour ce programme : E0061, « this function takes 2 arguments but 1 argument was supplied » (cette fonction prend 2 arguments, mais 1 seul a été fourni), sur area(1.0). Ce n’est qu’un effet secondaire. Rust a gardé le premier area et ignoré le second, alors l’appel avec un seul argument ne correspond plus. Corrigez E0428, et E0061 disparaît avec.
Ce que ça veut dire
Section intitulée « Ce que ça veut dire »rustc pointe deux endroits :
- La ligne
---(ligne 1) : « previous definition of the valueareahere » (définition précédente de la valeurarea). C’est le premierarea, celui que Rust garde. - La ligne
^^^(ligne 5) : «arearedefined here » (arearedéfini ici). C’est le second, celui qui est refusé. - La
note:dit queareane doit être défini qu’une seule fois « in the value namespace of this module » (dans l’espace de noms des valeurs de ce module). Chaque module est comme un tiroir, et dans un tiroir, chaque nom doit être unique.
Pour deux structs, la note parle plutôt de « type namespace » (espace de noms des types). Rust range les types (structs, enums, traits, modules) et les valeurs (fonctions, constantes, statics) dans deux listes séparées. C’est pour ça qu’un mod shapes et une fn shapes peuvent cohabiter, mais pas deux fn area ni deux struct User.
Pensez à deux fichiers nommés rapport.txt dans un même dossier. Votre ordinateur refuse le second, non pas parce qu’il a un problème, mais parce que rapport.txt ne lui dirait plus de quel fichier vous parlez.
Pourquoi Rust fait ça
Section intitulée « Pourquoi Rust fait ça »En Rust, un nom dans un module désigne exactement un élément. Quand vous écrivez area(...), Rust trouve la fonction par son nom seul, pas par le nombre ou le type des arguments. Ça rend le code simple à lire : il n’y a qu’un seul area à chercher, et ajouter une nouvelle fonction ne peut jamais changer en douce celle qu’utilise un ancien appel.
Les langages avec surcharge choisissent la version d’après les arguments, et les règles deviennent compliquées dès que les nombres se convertissent entre eux ou que des valeurs par défaut s’en mêlent. Rust a choisi de ne pas faire ça. Quand vous voulez « le même nom pour des entrées différentes », vous utilisez des traits ou des génériques, où le choix est visible dans les types. Pour en savoir plus sur les fonctions, voir Les paramètres.
Comment corriger
Section intitulée « Comment corriger »Solution 1 : donner à chacune son propre nom
Section intitulée « Solution 1 : donner à chacune son propre nom »Choisissez-la la plupart du temps. Deux fonctions qui prennent des entrées différentes font en général des travaux différents, et le nom peut dire lequel. Si l’une d’elles était une vieille copie que vous avez oublié de supprimer, supprimez-la :
fn rectangle_area(width: f64, height: f64) -> f64 { width * height}
fn circle_area(radius: f64) -> f64 { 3.14159 * radius * radius}
fn main() { println!("{}", rectangle_area(2.0, 3.0)); println!("{}", circle_area(1.0));}Sortie :
63.14159La bibliothèque standard fait pareil : String::new(), String::with_capacity(10) et String::from("hi") sont trois noms différents pour trois façons de créer une String.
Solution 2 : les mettre dans des modules séparés
Section intitulée « Solution 2 : les mettre dans des modules séparés »Choisissez-la quand le nom court est vraiment le meilleur dans chaque contexte, comme area pour chaque forme, ou un User dans votre code de base de données et un autre User dans votre code d’API. Chaque module a ses propres noms, et le conflit disparaît. Ceux qui appellent la fonction écrivent le nom du module devant :
mod rectangle { pub fn area(width: f64, height: f64) -> f64 { width * height }}
mod circle { pub fn area(radius: f64) -> f64 { 3.14159 * radius * radius }}
fn main() { println!("{}", rectangle::area(2.0, 3.0)); println!("{}", circle::area(1.0));}Sortie :
63.14159N’oubliez pas pub, sinon l’appel depuis main donne E0603, « function is private » (la fonction est privée). Voir Définir un module.
Solution 3 : utiliser un trait au lieu de la surcharge
Section intitulée « Solution 3 : utiliser un trait au lieu de la surcharge »Choisissez-la quand vous voulez un seul appel qui marche pour plusieurs types, ce qui est en général le but de la surcharge. Créez un type pour chaque sorte d’entrée, et donnez-leur un trait commun. Chaque forme a alors son propre area, et Rust choisit le bon d’après le type de la valeur :
trait Shape { fn area(&self) -> f64;}
struct Rectangle { width: f64, height: f64,}
struct Circle { radius: f64,}
impl Shape for Rectangle { fn area(&self) -> f64 { self.width * self.height }}
impl Shape for Circle { fn area(&self) -> f64 { 3.14159 * self.radius * self.radius }}
fn print_area<T: Shape>(shape: &T) { println!("Aire : {}", shape.area());}
fn main() { print_area(&Rectangle { width: 2.0, height: 3.0 }); print_area(&Circle { radius: 1.0 });}Sortie :
Aire : 6Aire : 3.14159C’est plus de code que la solution 1, cela vaut donc le coup quand vous avez plusieurs formes ou plusieurs fonctions (area, perimeter, draw) qui suivent toutes le même modèle. La fonction générique print_area<T: Shape> marche pour toutes les formes que vous ajouterez plus tard. Voir Les traits comme paramètres de fonction et Les génériques.
Pour « la même fonction avec ou sans un argument en plus », il existe une option plus légère : prenez une Option, comme fn greet(name: &str, title: Option<&str>), et appelez-la avec None ou Some("Dr.").
Solution 4 : renommer un import avec use ... as ...
Section intitulée « Solution 4 : renommer un import avec use ... as ... »Choisissez-la quand le conflit est entre votre propre élément et une ligne use. À proprement parler, c’est une erreur sœur, E0255, avec le même titre :
error[E0255]: the name `Error` is defined multiple times --> src/main.rs:3:1 |1 | use std::io::Error; | -------------- previous import of the type `Error` here2 |3 | enum Error { | ^^^^^^^^^^ `Error` redefined here | = note: `Error` must be defined only once in the type namespace of this modulehelp: you can use `as` to change the binding name of the import |1 | use std::io::Error as OtherError; | +++++++++++++Ici, « previous import » veut dire « import précédent », et l’aide vous propose d’utiliser as pour changer le nom sous lequel le type est importé. Donnez à l’import un surnom local avec as. Choisissez un nom qui dit d’où il vient, comme IoError, plutôt que le nom provisoire OtherError de rustc :
use std::io::Error as IoError;use std::io::ErrorKind;
#[derive(Debug)]enum Error { NotFound(String), Invalid(u32),}
fn main() { let mine = Error::NotFound(String::from("config.toml")); let other = Error::Invalid(42); let disk = IoError::new(ErrorKind::Other, "disque plein");
println!("{:?}", mine); println!("{:?}", other); println!("{}", disk);}Sortie :
NotFound("config.toml")Invalid(42)disque pleinDeux lignes use en conflit, comme use std::fmt::Result; et use std::io::Result;, donnent une troisième erreur sœur, E0252 (`Result` reimported here, Result importé une seconde fois ici). La correction est la même : use std::io::Result as IoResult;, ou importez le module (use std::io;) et écrivez io::Result. Renommer votre propre enum (AppError) marche aussi.
Où vous le verrez souvent
Section intitulée « Où vous le verrez souvent »Surcharger une méthode dans un bloc impl. Deux fn new dans impl Timer donnent un autre code, E0592 : duplicate definitions with name `new` (définitions en double pour le nom new). Même cause, même correction : nommez-les d’après ce qu’elles font, comme Timer::new() et Timer::with_seconds(30). Voir Les structs.
Un copier-coller pendant une refactorisation. Vous copiez une fonction pour la modifier, ou vous collez une struct User venant d’un autre fichier, et vous oubliez de supprimer l’ancienne. C’est fréquent aussi dans les tests : deux #[test] fn it_works() dans un même mod tests donnent E0428. Correction : supprimez l’ancienne copie, ou renommez le test.
Des variantes d’enum en double. enum Color { Red, Red } donne aussi E0428, en pointant le second Red, avec la note « type namespace of this enum » (espace de noms des types de cet enum). Correction : supprimez ou renommez la variante en double. Voir Les enums.
Erreurs liées
Section intitulée « Erreurs liées »- E0061 : mauvais nombre d’arguments. Souvent affichée juste après E0428 quand vous avez essayé de surcharger une fonction.
- E0425 : le problème inverse, un nom défini zéro fois là où vous l’utilisez.
- E0433 : un chemin comme
circle::areadont la première partie (le module) est introuvable. - E0034 : deux traits donnent à un type des méthodes du même nom, et vous devez dire laquelle vous voulez.
- E0255 (un
useentre en conflit avec un élément local), E0252 (deux lignesuseen conflit), E0592 (méthode en double dans unimpl) : les erreurs sœurs présentées plus haut. - Toutes les erreurs du compilateur : la liste complète des pages d’erreurs.
Petit quiz
Section intitulée « Petit quiz »Petit quiz
1. Vous écrivez fn total(a: u32) et fn total(a: u32, b: u32) dans un même fichier. Que se passe-t-il ?
2. Un mod report et une fn report() peuvent-ils cohabiter dans le même module ?
3. Votre fichier contient use std::fmt::Result; et vous définissez enum Result. Quelle est une correction propre ?
Score : 0 / 3
Pour aller plus loin
Section intitulée « Pour aller plus loin »- E0428 — Rust error codes index (en anglais) : l’explication officielle, avec l’exemple des deux structs.
- Defining Modules to Control Scope and Privacy — The Rust Book (en anglais) : comment les modules donnent à chaque partie de votre code ses propres noms.
- Providing New Names with the
asKeyword — The Rust Book (en anglais) : l’exemplefmt::Resultetio::Result, résolu avecas. - Traits — The Rust Book (en anglais) : partager un comportement entre plusieurs types, la réponse de Rust à la surcharge.
- Namespaces — The Rust Reference (en anglais) : les règles exactes des espaces de noms des types et des valeurs dont parle la note de l’erreur.