Aller au contenu

Erreur Rust E0252 : the name is defined multiple times (deux imports)

E0252 veut dire que deux lignes use importent le même nom. Le cas d’école : use std::fmt::Result; plus use std::io::Result;, deux types différents qui s’appellent tous les deux Result. On le voit aussi avec fmt::Error et io::Error, ou avec les deux traits Write. C’est une erreur très courante, et facile à corriger, en général en changeant une ou deux lignes use. rustc 1.97 l’affiche toujours sous son propre code, E0252. Cette page fait partie du guide des erreurs du compilateur Rust.

error[E0252]: the name `Result` is defined multiple times
--> src/main.rs:2:5
|
1 | use std::fmt::Result;
| ---------------- previous import of the type `Result` here
2 | use std::io::Result; // ❌ un deuxième `Result`
| ^^^^^^^^^^^^^^^ `Result` reimported here
|
= note: `Result` 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
|
2 | use std::io::Result as OtherResult; // ❌ un deuxième `Result`
| ++++++++++++++

Le compilateur vous dit : « le nom Result est défini plusieurs fois ». La section « Ce que ça veut dire » ci-dessous traduit chaque ligne.

Voici le programme qui la provoque :

use std::fmt::Result;
use std::io::Result; // ❌ un deuxième `Result`
struct Temperature {
celsius: i32,
}
impl std::fmt::Display for Temperature {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result {
write!(f, "{} °C", self.celsius)
}
}
fn parse_temperature(text: &str) -> Result<Temperature> {
match text.trim().parse() {
Ok(celsius) => Ok(Temperature { celsius }),
Err(_) => Err(std::io::Error::other("not a number")),
}
}
fn main() {
match parse_temperature("21") {
Ok(t) => println!("{}", t),
Err(e) => println!("error: {}", e),
}
}

rustc affiche quatre autres erreurs après celle-ci : E0107 (« type alias takes 0 generic arguments », l’alias de type ne prend aucun argument générique), deux E0308 (types incompatibles) et une E0277. Ne les pourchassez pas. Rust a gardé le premier import, donc chaque Result du fichier désigne maintenant fmt::Result, et parse_temperature n’a plus de sens. Corrigez E0252, et elles disparaissent toutes.

rustc pointe deux lignes use :

  1. La ligne --- (ligne 1) : « previous import of the type Result here » (import précédent du type Result). Celui-ci est arrivé en premier, c’est donc lui que Rust garde.
  2. La ligne ^^^ (ligne 2) : « Result reimported here » (Result importé une seconde fois ici). Le second import essaie de prendre le même nom.
  3. Le help: propose de renommer le second import avec as.

std::fmt::Result et std::io::Result sont vraiment différents. fmt::Result est un simple Result<(), fmt::Error>, utilisé par Display et Debug. io::Result<T> est un Result<T, io::Error>, utilisé pour les fichiers et le réseau. Même nom, rôles différents.

C’est comme enregistrer deux contacts sous le nom « Maman » dans votre téléphone : votre mère et la mère de votre conjoint. Quand vous touchez « Maman », le téléphone doit savoir qui appeler. Vous ajoutez un mot à l’un des deux, ou vous les enregistrez sous « Maman (Ana) » et « Maman (Lee) ».

Dans un module, chaque nom désigne exactement un élément. Une ligne use ne copie rien : elle ajoute un nom court pour un élément qui vit ailleurs. Deux lignes use qui ajoutent le même nom court rendraient ambigu chaque Result qui suit, alors Rust s’arrête à l’import au lieu de deviner à chaque utilisation.

E0252 fait partie de trois erreurs sœurs avec le même titre : E0255, c’est une ligne use contre votre propre élément, et E0428, deux de vos propres éléments. La règle derrière les trois : « un nom, une seule chose, par module ». Voir use : raccourcir les chemins.

Solution 1 : importer les modules et écrire fmt:: et io::

Section intitulée « Solution 1 : importer les modules et écrire fmt:: et io:: »

Choisissez-la la plupart du temps, et toujours pour Result et Error. Importez les modules parents, puis écrivez le nom du module devant. Chaque utilisation dit alors de quel Result il s’agit. Le Rust Book recommande ce style justement pour ce cas :

use std::fmt;
use std::io;
struct Temperature {
celsius: i32,
}
impl fmt::Display for Temperature {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} °C", self.celsius)
}
}
fn parse_temperature(text: &str) -> io::Result<Temperature> {
match text.trim().parse() {
Ok(celsius) => Ok(Temperature { celsius }),
Err(_) => Err(io::Error::other("ce n'est pas un nombre")),
}
}
fn main() {
for input in ["21", " -4 ", "tiède"] {
match parse_temperature(input) {
Ok(t) => println!("{}", t),
Err(e) => println!("erreur : {}", e),
}
}
}

Sortie :

21 °C
-4 °C
erreur : ce n'est pas un nombre
▶ Essayez par vous-même

En bonus : le Result<T, E> ordinaire du prélude reste disponible, sans aucun import.

Choisissez-la quand vous utilisez beaucoup l’un des deux types et que vous voulez un nom court pour lui. C’est la suggestion de rustc. Choisissez un nom qui dit d’où il vient, comme IoResult, plutôt que OtherResult :

use std::fmt::Result;
use std::io::Result as IoResult;
struct Temperature {
celsius: i32,
}
impl std::fmt::Display for Temperature {
fn fmt(&self, f: &mut std::fmt::Formatter) -> Result {
write!(f, "{} °C", self.celsius)
}
}
fn parse_temperature(text: &str) -> IoResult<Temperature> {
match text.trim().parse() {
Ok(celsius) => Ok(Temperature { celsius }),
Err(_) => Err(std::io::Error::other("ce n'est pas un nombre")),
}
}
fn main() {
match parse_temperature("21") {
Ok(t) => println!("{}", t),
Err(e) => println!("erreur : {}", e),
}
}

Sortie :

21 °C
▶ Essayez par vous-même

Un simple Result qui veut dire en secret fmt::Result peut surprendre la prochaine personne qui lit le code, donc la solution 1 est en général plus claire. as est surtout utile pour les noms longs, comme use std::collections::BTreeMap as Map;.

Choisissez-la pour les traits que vous n’importez que pour appeler leurs méthodes. Les deux traits Write sont l’exemple classique : fmt::Write permet d’utiliser write! dans une String, io::Write permet d’utiliser write! vers la sortie standard ou un fichier. Les importer tous les deux par leur nom crée un conflit :

error[E0252]: the name `Write` is defined multiple times
--> src/main.rs:2:5
|
1 | use std::fmt::Write;
| --------------- previous import of the trait `Write` here
2 | use std::io::Write; // ❌ un deuxième `Write`
| ^^^^^^^^^^^^^^ `Write` reimported here
|
= note: `Write` 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
|
2 | use std::io::Write as OtherWrite; // ❌ un deuxième `Write`
| +++++++++++++

Le programme qui ne compile pas :

use std::fmt::Write;
use std::io::Write; // ❌ un deuxième `Write`
fn main() {
let mut report = String::new();
write!(report, "{} tests passed", 12).unwrap();
let mut out = std::io::stdout();
writeln!(out, "{}", report).unwrap();
}

Ici, vous n’écrivez jamais Write vous-même : vous avez seulement besoin de ses méthodes. as _ importe un trait sans lui donner de nom, donc aucun conflit n’est possible, et les deux séries de méthodes fonctionnent quand même :

use std::fmt::Write as _;
use std::io::Write as _;
fn main() {
let mut report = String::new();
write!(report, "{} tests réussis", 12).unwrap();
let mut out = std::io::stdout();
writeln!(out, "{}", report).unwrap();
}

Sortie :

12 tests réussis
▶ Essayez par vous-même

Si vous devez nommer l’un des deux, par exemple dans fn log(w: &mut impl io::Write), combinez avec la solution 1 : use std::io; et écrivez io::Write. Voir Les traits.

Solution 4 : supprimer un import en double du même élément

Section intitulée « Solution 4 : supprimer un import en double du même élément »

Choisissez-la quand les deux lignes importent exactement la même chose, ce qui arrive souvent après une fusion de code, ou quand votre éditeur ajoute un import que vous aviez déjà :

error[E0252]: the name `HashMap` is defined multiple times
--> src/main.rs:2:24
|
1 | use std::collections::HashMap;
| ------------------------- previous import of the type `HashMap` here
2 | use std::collections::{HashMap, HashSet}; // ❌ encore `HashMap`
| ^^^^^^^--
| |
| `HashMap` reimported here
| help: remove unnecessary import

Le programme qui ne compile pas :

use std::collections::HashMap;
use std::collections::{HashMap, HashSet}; // ❌ encore `HashMap`
fn main() {
let mut stock: HashMap<&str, u32> = HashMap::new();
stock.insert("apples", 3);
let mut sold_out: HashSet<&str> = HashSet::new();
sold_out.insert("pears");
println!("apples: {}, sold out: {}", stock["apples"], sold_out.len());
}

Remarquez que l’aide est différente : pas de as, juste « remove unnecessary import » (supprimez l’import inutile). Gardez une seule ligne :

use std::collections::{HashMap, HashSet};
fn main() {
let mut stock: HashMap<&str, u32> = HashMap::new();
stock.insert("pommes", 3);
let mut sold_out: HashSet<&str> = HashSet::new();
sold_out.insert("poires");
println!("pommes : {}, épuisés : {}", stock["pommes"], sold_out.len());
}

Sortie :

pommes : 3, épuisés : 1
▶ Essayez par vous-même

Voir Les HashMap et Les HashSet.

Error venant de deux endroits. use std::fmt::Error; et use std::io::Error;, ou use std::error::Error; à côté de use anyhow::Error;. Correction : use std::io; et écrivez io::Error, ou use anyhow::Error as AnyError;. Voir anyhow et thiserror.

Deux crates avec le même nom de type. use chrono::Duration; et use std::time::Duration; forment une paire fréquente dans les projets qui manipulent des dates. Correction : gardez un nom court pour l’un, et écrivez le module de l’autre, comme chrono::Duration. Voir Les dates et heures.

Les imports glob. use std::fmt::*; plus use std::io::*; ne donne pas d’E0252 sur les lignes use. Le problème arrive plus tard, au premier Result, sous la forme d’une erreur sans code : error: `Result` is ambiguous (Result est ambigu). Correction : importez explicitement les noms dont vous avez besoin, comme dans la solution 1.

  • E0255 : une ligne use entre en conflit avec votre propre struct, enum ou fonction du même nom.
  • E0428 : deux de vos propres éléments portent le même nom, souvent parce que vous avez essayé de surcharger une fonction.
  • E0432 : import non résolu, quand la ligne use pointe vers quelque chose qui n’existe pas.
  • E0034 : deux traits visibles donnent à un type des méthodes du même nom.
  • E0308 : types incompatibles, l’une des erreurs qui suivent un conflit de Result.
  • Toutes les erreurs du compilateur : la liste complète des pages d’erreurs.

Petit quiz

1. Vous avez besoin de fmt::Result et de io::Result dans le même fichier. Quels imports sont le choix propre habituel ?

2. Vous avez besoin des méthodes de std::fmt::Write et de std::io::Write, mais vous n'écrivez jamais vous-même le nom des traits. Qu'est-ce qui marche ?

3. rustc dit que HashMap est « reimported here » et propose remove unnecessary import. Que s'est-il passé ?

Score : 0 / 3