Aller au contenu

Erreur Rust E0592 : duplicate definitions with name X (méthode en double)

E0592 veut dire que le même type a deux méthodes, fonctions associées ou constantes du même nom, écrites dans des blocs impl. Un type peut avoir autant de blocs impl que vous voulez, mais ils partagent tous une seule liste de noms, et chaque nom ne peut servir qu’une fois. L’erreur vient en général d’un copier-coller, d’un merge, ou d’un deuxième bloc impl dans un autre fichier. La correction : en garder un, ou donner un nouveau nom à l’autre. Cette page fait partie du guide des erreurs du compilateur Rust.

error[E0592]: duplicate definitions with name `reset`
--> src/main.rs:10:5
|
10 | fn reset(&mut self) {
| ^^^^^^^^^^^^^^^^^^^ duplicate definitions for `reset`
...
16 | fn reset(&mut self) { // ❌ `reset` est déjà défini plus haut
| ------------------- other definition for `reset`

Le compilateur vous dit : « définitions en double pour le nom reset ». La ligne --- marque l’« other definition » (l’autre définition).

Voici le programme qui la provoque :

struct Counter {
count: u32,
}
impl Counter {
fn new() -> Self {
Counter { count: 0 }
}
fn reset(&mut self) {
self.count = 0;
}
}
impl Counter {
fn reset(&mut self) { // ❌ `reset` est déjà défini plus haut
self.count = 0;
}
}
fn main() {
let mut c = Counter::new();
c.reset();
println!("{}", c.count);
}

rustc signale aussi E0034, « multiple applicable items in scope » (plusieurs éléments applicables dans la portée), sur l’appel c.reset(), car il ne sait pas lequel des deux reset vous voulez. C’est une erreur secondaire : corrigez E0592, et E0034 disparaît avec.

Le titre de l’erreur donne le nom défini deux fois (reset). Le ^^^ marque une définition, et la ligne --- marque l’« other definition ». rustc affiche les numéros de ligne des deux, pour que vous puissiez y aller directement. Quand elles sont loin l’une de l’autre, les ... entre les deux veulent juste dire « quelques lignes sautées ».

Peu importe que les deux reset soient dans des blocs impl Counter différents, ou même dans des fichiers différents de votre crate. Pour Rust, ce sont toutes des méthodes du même type, donc Counter::reset doit désigner exactement une fonction. La même règle s’applique aux constantes associées : deux const MAX: u32 pour un même type, c’est aussi E0592.

C’est comme un répertoire de téléphone avec deux contacts appelés « Maman ». Quand vous dites « appelle Maman », quel numéro doit-il composer ?

Répartir les méthodes sur plusieurs blocs impl est permis et souvent utile : un bloc pour les constructeurs, un pour les accesseurs, un derrière un #[cfg(test)]. Voir Ajouter du comportement avec des méthodes. Mais quand vous appelez c.reset() ou Counter::reset(&mut c), Rust doit trouver une fonction. S’il y en avait deux, celle qui s’exécute dépendrait de détails que personne ne voit en lisant l’appel.

Rust refuse donc dès la définition, pas seulement à l’appel. Ça attrape un vrai bug : avec une méthode copiée-collée, vous pourriez corriger un problème dans une copie pendant que l’autre continue de tourner. Rust s’assure qu’il n’y a toujours qu’une seule version à corriger.

Solution 1 : supprimer le doublon et garder une seule définition

Section intitulée « Solution 1 : supprimer le doublon et garder une seule définition »

Choisissez-la la plupart du temps. Si les deux copies font la même chose (copier-coller, merge raté, méthode déplacée mais pas supprimée), supprimez-en une. Avant de supprimer, comparez les deux corps : si l’un contient une correction que l’autre n’a pas, gardez celui-là. Fusionner les blocs impl en un seul rend aussi les doublons plus faciles à repérer à l’avenir.

struct Counter {
count: u32,
}
impl Counter {
fn new() -> Self {
Counter { count: 0 }
}
fn increment(&mut self) {
self.count += 1;
}
fn reset(&mut self) { // ✅ un seul `reset` pour `Counter`
self.count = 0;
}
}
fn main() {
let mut c = Counter::new();
c.increment();
c.increment();
println!("avant reset : {}", c.count);
c.reset();
println!("après reset : {}", c.count);
}

Sortie :

avant reset : 2
après reset : 0
▶ Essayez par vous-même

Si vous ne trouvez pas la deuxième définition, cherchez dans tout le projet : grep -rn "fn reset" src/. Un bloc impl Counter peut se trouver dans n’importe quel module de votre crate.

Choisissez-la quand les deux définitions font des choses différentes. Rust n’a pas de surcharge (overloading) : vous ne pouvez pas avoir deux méthodes du même nom avec des paramètres différents, comme reset() et reset(start: u32). C’est aussi E0592. Donnez à chacune un nom qui dit ce qu’elle fait :

struct Counter {
count: u32,
}
impl Counter {
fn new() -> Self {
Counter { count: 0 }
}
fn reset(&mut self) {
self.count = 0;
}
}
impl Counter {
fn reset_to(&mut self, start: u32) { // ✅ un nom différent pour un comportement différent
self.count = start;
}
}
fn main() {
let mut c = Counter::new();
c.reset_to(10);
println!("après reset_to(10) : {}", c.count);
c.reset();
println!("après reset() : {}", c.count);
}

Sortie :

après reset_to(10) : 10
après reset() : 0
▶ Essayez par vous-même

Les conventions de nommage courantes en Rust aident ici : new et with_capacity, from_str et from_bytes, get et get_mut. Si vous voulez vraiment un seul nom pour plusieurs types d’arguments, utilisez un paramètre générique ou un trait, comme fn reset_to(&mut self, start: impl Into<u32>). Voir Les génériques.

Solution 3 : empêcher les blocs impl génériques de se chevaucher

Section intitulée « Solution 3 : empêcher les blocs impl génériques de se chevaucher »

Choisissez-la quand l’une des deux définitions est dans un bloc générique comme impl<T> Wrapper<T>. Ce bloc couvre aussi Wrapper<i32>, donc un deuxième impl Wrapper<i32> avec le même nom de méthode est un doublon :

error[E0592]: duplicate definitions with name `describe`
--> src/main.rs:4:5
|
4 | fn describe(&self) -> String {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ duplicate definitions for `describe`
...
10 | fn describe(&self) -> String {
| ---------------------------- other definition for `describe`

Le programme qui la provoque :

struct Wrapper<T>(T);
impl<T> Wrapper<T> {
fn describe(&self) -> String {
String::from("quelque chose")
}
}
impl Wrapper<i32> {
fn describe(&self) -> String {
format!("le nombre {}", self.0)
}
}
fn main() {
println!("{}", Wrapper(5).describe());
}

Rust ne permet pas à un impl spécifique de remplacer un impl générique (cette fonctionnalité, appelée specialization ou spécialisation, n’est pas stable). Deux bonnes façons de s’en sortir : n’écrire des blocs impl que pour des types précis, pour qu’ils ne puissent pas se chevaucher, ou mettre le comportement dans un trait et l’implémenter une fois par type :

struct Wrapper<T>(T);
// Option A : des blocs impl pour des types précis ne se chevauchent jamais
impl Wrapper<i32> {
fn describe(&self) -> String {
format!("le nombre {}", self.0)
}
}
impl Wrapper<&str> {
fn describe(&self) -> String {
format!("le texte \"{}\"", self.0)
}
}
// Option B : un trait, implémenté une fois par type
trait Label {
fn label(&self) -> String;
}
impl Label for i32 {
fn label(&self) -> String {
format!("entier {}", self)
}
}
impl Label for bool {
fn label(&self) -> String {
format!("booléen {}", self)
}
}
impl<T: Label> Wrapper<T> {
fn label(&self) -> String {
self.0.label() // une seule méthode générique, la partie propre à chaque type vit dans le trait
}
}
fn main() {
println!("{}", Wrapper(5).describe());
println!("{}", Wrapper("salut").describe());
println!("{}", Wrapper(7).label());
println!("{}", Wrapper(true).label());
}

Sortie :

le nombre 5
le texte "salut"
entier 7
booléen true
▶ Essayez par vous-même

La version avec un trait est en général la meilleure conception sur le long terme : prendre en charge un nouveau type, c’est juste un impl Label for ... de plus. Voir Les traits.

Une fonction d’aide pour les tests dans un deuxième bloc impl. Vous ajoutez impl Counter { fn new() -> Self { ... } } dans #[cfg(test)] mod tests pour construire des valeurs de test, mais Counter::new existe déjà. Le bloc de test reste un impl Counter. Correction : appelez-la par exemple fn for_test(), ou utilisez le vrai new. Voir Les tests.

Après un merge ou une refactorisation. Deux branches ont chacune ajouté un fn len(&self), ou une méthode a été déplacée dans un nouveau fichier et l’ancienne copie est restée. rustc affiche les deux numéros de ligne ; gardez la meilleure version.

Une méthode de trait du même nom, ça passe. impl Counter { fn reset(&mut self) } avec impl Resettable for Counter { fn reset(&mut self) }, ce n’est pas E0592 : l’une est la méthode propre du type, l’autre appartient au trait, et c.reset() appelle celle du type. Deux traits avec le même nom de méthode compilent aussi, mais l’appel c.reset() donne alors E0034 ; corrigez-le avec Resettable::reset(&mut c).

Petit quiz

1. Un type a deux blocs impl. Peuvent-ils contenir chacun une méthode appelée reset ?

2. Vous voulez area() et area(scale: f64). Que faites-vous en Rust ?

3. Pourquoi impl<T> Wrapper<T> et impl Wrapper<i32> entrent-ils en conflit quand les deux définissent describe ?

Score : 0 / 3