Aller au contenu

Erreur Rust E0255 : the name is defined multiple times (import contre type local)

E0255 veut dire qu’une ligne use et quelque chose que vous avez défini vous-même portent le même nom. Le cas classique : vous importez std::io::Error en haut du fichier, puis vous écrivez plus bas votre propre enum Error. Rust ne peut pas laisser un même nom désigner deux choses, alors il s’arrête. C’est une erreur très fréquente quand on commence à écrire ses propres types d’erreur, et la correction tient en une petite ligne. Contrairement à E0412, ce code n’a pas changé dans les versions récentes : rustc 1.97 l’affiche toujours sous le code E0255. Cette page fait partie du guide des erreurs du compilateur Rust.

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 { // ❌ même nom que l'import de la ligne 1
| ^^^^^^^^^^ `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;
| +++++++++++++
warning: unused import: `std::io::Error`
--> src/main.rs:1:5
|
1 | use std::io::Error;
| ^^^^^^^^^^^^^^

Le compilateur vous dit : « le nom Error 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::io::Error;
enum Error { // ❌ même nom que l'import de la ligne 1
EmptyName,
TooLong(usize),
}
fn check_name(name: &str) -> Result<(), Error> {
if name.is_empty() {
return Err(Error::EmptyName);
}
if name.len() > 10 {
return Err(Error::TooLong(name.len()));
}
Ok(())
}
fn main() {
match check_name("") {
Ok(()) => println!("name is fine"),
Err(Error::EmptyName) => println!("name is empty"),
Err(Error::TooLong(n)) => println!("name has {} letters, max is 10", n),
}
}

rustc pointe trois choses :

  1. La ligne --- (ligne 1) : « previous import of the type Error here » (import précédent du type Error). La ligne use a déjà placé un nom Error dans ce module.
  2. La ligne ^^^ (ligne 3) : « Error redefined here » (Error redéfini ici). Votre propre enum Error essaie de prendre le même nom.
  3. Le help: propose une porte de sortie : donner un autre nom local à l’import avec as.

L’avertissement warning: unused import (import inutilisé) juste en dessous est un indice précieux. Il veut dire que rien dans votre code n’utilise vraiment std::io::Error : chaque Error de ce programme désigne votre enum. Dans ce cas, vous pouvez tout simplement supprimer la ligne 1. Si vous avez besoin des deux, lisez la suite.

La note parle du type namespace (espace de noms des types). Les types, les traits et les modules partagent une même liste de noms ; les fonctions, les constantes et les statics en partagent une autre. Donc use std::mem::swap; plus votre propre fn swap donne la même erreur, mais dans le « value namespace » (espace de noms des valeurs).

Imaginez une classe avec deux élèves qui s’appellent Alex. L’enseignant ne peut plus simplement appeler « Alex ». Il faut un surnom à l’un des deux, comme « Alex B. », et c’est exactement ce que as donne à un import.

Dans un module, un nom désigne exactement un élément. Quand Rust lit Error::EmptyName à la ligne 10, il doit savoir de quel Error vous parlez, et il refuse de deviner. S’il choisissait en douce votre enum, votre ligne use ne servirait à rien ; s’il choisissait io::Error, votre enum deviendrait inaccessible. Dans les deux cas, un choix silencieux cacherait une erreur.

C’est pour ça qu’E0255 est la sœur d’E0428 (deux de vos propres éléments avec un même nom) et d’E0252 (deux lignes use avec un même nom). La règle est la même : un nom, une seule chose, par module. Voir use : raccourcir les chemins pour comprendre comment fonctionne use.

Choisissez-la quand vous avez besoin des deux types dans le même fichier, ce qui est courant : votre propre Error pour les règles de votre application, et io::Error pour les fichiers et le réseau. Donnez à l’import un surnom qui dit d’où il vient. IoError se lit mieux que le nom provisoire OtherError proposé par rustc :

use std::io::Error as IoError;
use std::io::ErrorKind;
#[derive(Debug)]
enum Error {
EmptyName,
TooLong(usize),
}
fn check_name(name: &str) -> Result<(), Error> {
if name.is_empty() {
return Err(Error::EmptyName);
}
if name.len() > 10 {
return Err(Error::TooLong(name.len()));
}
Ok(())
}
fn save_name(name: &str) -> Result<(), IoError> {
if name == "root" {
return Err(IoError::new(ErrorKind::PermissionDenied, "ce nom est réservé"));
}
Ok(())
}
fn main() {
println!("{:?}", check_name("Ferris"));
println!("{:?}", check_name(""));
println!("{:?}", check_name("Ferris the Crab"));
match save_name("root") {
Ok(()) => println!("enregistré"),
Err(e) => println!("erreur d'E/S : {}", e),
}
}

Sortie :

Ok(())
Err(EmptyName)
Err(TooLong(15))
erreur d'E/S : ce nom est réservé
▶ Essayez par vous-même

as ne change le nom que dans ce fichier. Partout ailleurs, le type reste std::io::Error.

Choisissez-la quand vous voulez que chaque Error montre d’où il vient, à chaque utilisation. Importez le module parent avec use std::io; et écrivez io::Error. Votre propre Error garde le nom court. Beaucoup de développeurs Rust préfèrent ce style pour les types aux noms très courants, comme Error et Result :

use std::io;
enum Error {
EmptyName,
}
fn check_name(name: &str) -> Result<(), Error> {
if name.is_empty() {
return Err(Error::EmptyName);
}
Ok(())
}
fn save_name(name: &str) -> Result<(), io::Error> {
if name == "root" {
return Err(io::Error::new(io::ErrorKind::PermissionDenied, "nom réservé"));
}
Ok(())
}
fn main() {
if let Err(Error::EmptyName) = check_name("") {
println!("le nom est vide");
}
if let Err(e) = save_name("root") {
println!("erreur d'E/S : {}", e);
}
}

Sortie :

le nom est vide
erreur d'E/S : nom réservé
▶ Essayez par vous-même

Choisissez-la quand votre type sera utilisé dans beaucoup de fichiers, ou par d’autres personnes. Un nom précis comme NameError ou AppError est plus clair qu’un deuxième Error, et la ligne use peut rester telle quelle :

use std::io::Error;
#[derive(Debug)]
enum NameError {
EmptyName,
TooLong(usize),
}
fn check_name(name: &str) -> Result<(), NameError> {
if name.is_empty() {
return Err(NameError::EmptyName);
}
if name.len() > 10 {
return Err(NameError::TooLong(name.len()));
}
Ok(())
}
fn main() {
println!("{:?}", check_name("Ferris the Crab"));
let disk = Error::other("le disque est plein");
println!("{}", disk);
}

Sortie :

Err(TooLong(15))
le disque est plein
▶ Essayez par vous-même

Pour les projets plus grands, la crate thiserror rend ce genre d’enum d’erreurs beaucoup plus court à écrire. Voir Les erreurs personnalisées.

Solution 4 : supprimer l’import que vous n’utilisez pas

Section intitulée « Solution 4 : supprimer l’import que vous n’utilisez pas »

Choisissez-la quand rustc affiche aussi warning: unused import, comme dans l’erreur ci-dessus. L’import est un reste d’un ancien code, ou votre éditeur l’a ajouté tout seul. Supprimez la ligne, et le programme du haut de cette page fonctionne tel quel :

enum Error {
EmptyName,
TooLong(usize),
}
fn check_name(name: &str) -> Result<(), Error> {
if name.is_empty() {
return Err(Error::EmptyName);
}
if name.len() > 10 {
return Err(Error::TooLong(name.len()));
}
Ok(())
}
fn main() {
match check_name("") {
Ok(()) => println!("le nom est correct"),
Err(Error::EmptyName) => println!("le nom est vide"),
Err(Error::TooLong(n)) => println!("le nom fait {} lettres, 10 au maximum", n),
}
}

Sortie :

le nom est vide
▶ Essayez par vous-même

Votre propre fonction avec un nom de la bibliothèque standard. use std::mem::swap; plus fn swap(a: &mut i32, b: &mut i32) donne E0255 avec « previous import of the value swap here » et la note « value namespace » (espace de noms des valeurs). Correction : appelez la vôtre swap_values, ou supprimez l’import si vous vouliez écrire la vôtre.

Un copier-coller entre modules. Vous déplacez struct User dans mod models, vous ajoutez use models::User; en haut, mais vous oubliez de supprimer l’ancienne struct User dans main.rs. Correction : supprimez l’ancienne copie, pour que User ne désigne plus que celle de models.

Un trait avec un nom de la bibliothèque standard. use std::fmt::Display; et votre propre trait Display entrent en conflit de la même façon (« previous import of the trait Display »). Correction : renommez votre trait (Describe, Render), ou écrivez use std::fmt; et fmt::Display.

Bon à savoir : un import glob (avec étoile) comme use std::io::*; ne provoque jamais E0255. Vos propres éléments l’emportent discrètement sur les noms qui arrivent par un *. C’est pratique, mais cela peut aussi cacher quel Error vous utilisez, donc des imports explicites sont en général plus clairs.

  • E0252 : deux lignes use importent le même nom, comme fmt::Result et io::Result.
  • E0428 : deux de vos propres éléments portent le même nom, souvent parce que vous avez essayé de surcharger une fonction.
  • E0592 : deux méthodes du même nom dans un bloc impl.
  • E0432 : la ligne use elle-même pointe vers quelque chose qui n’existe pas.
  • E0425 : le problème inverse, un nom qui n’est défini nulle part là où vous l’utilisez.
  • Toutes les erreurs du compilateur : la liste complète des pages d’erreurs.

Petit quiz

1. Votre fichier commence par use std::fmt::Result; et définit plus bas enum Result { Pass, Fail }. Quelle erreur obtenez-vous ?

2. Vous avez besoin de std::io::Error et de votre propre enum Error dans un même fichier. Quelle est une correction propre ?

3. rustc affiche E0255 et aussi warning: unused import: std::io::Error. Que vous dit cet avertissement ?

Score : 0 / 3