Aller au contenu

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.

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 module

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

rustc pointe deux endroits :

  1. La ligne --- (ligne 1) : « previous definition of the value area here » (définition précédente de la valeur area). C’est le premier area, celui que Rust garde.
  2. La ligne ^^^ (ligne 5) : « area redefined here » (area redéfini ici). C’est le second, celui qui est refusé.
  3. La note: dit que area ne 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.

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.

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 :

6
3.14159
▶ Essayez par vous-même

La 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 :

6
3.14159
▶ Essayez par vous-même

N’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 : 6
Aire : 3.14159
▶ Essayez par vous-même

C’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` here
2 |
3 | enum Error {
| ^^^^^^^^^^ `Error` redefined here
|
= note: `Error` must be defined only once in the type namespace of this module
help: 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 plein
▶ Essayez par vous-même

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

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.

  • 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::area dont 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 use entre en conflit avec un élément local), E0252 (deux lignes use en conflit), E0592 (méthode en double dans un impl) : les erreurs sœurs présentées plus haut.
  • Toutes les erreurs du compilateur : la liste complète des pages d’erreurs.

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