Salta ai contenuti

Errore Rust E0255: the name is defined multiple times (import e item locale)

E0255 significa che una riga use e qualcosa che hai definito tu hanno lo stesso nome. Il caso classico: importi std::io::Error in cima al file e più sotto scrivi il tuo enum Error. Rust non può lasciare che un nome significhi due cose, quindi si ferma. È un errore molto comune quando inizi a scrivere i tuoi tipi di errore, e la correzione è una riga corta. A differenza di E0412, questo codice non è cambiato nelle versioni recenti: rustc 1.97 lo segnala ancora come E0255. Fa parte della guida agli errori del compilatore.

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 { // ❌ same name as the import on line 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;
| ^^^^^^^^^^^^^^

Il compilatore dice: il nome Error è definito più volte, una volta dall’import e una volta dal tuo enum. Questo è il programma che produce l’errore:

use std::io::Error;
enum Error { // ❌ same name as the import on line 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 ti indica tre cose:

  1. La riga con --- (riga 1): «previous import of the type Error here» (import precedente del tipo Error). La riga use ha già messo un nome Error in questo modulo.
  2. La riga con ^^^ (riga 3): «Error redefined here» (Error ridefinito qui). Il tuo enum Error cerca di prendere lo stesso nome.
  3. L’help: ti offre una via d’uscita: dare all’import un nome locale diverso con as.

Il warning: unused import più sotto è un indizio utile. Significa che niente nel tuo codice usa davvero std::io::Error (ogni Error in questo programma indica il tuo enum). In quel caso puoi semplicemente cancellare la riga 1. Se invece ti servono tutti e due, continua a leggere.

La nota parla del type namespace (lo spazio dei nomi dei tipi). Tipi, trait e moduli condividono una lista di nomi; funzioni, costanti e static ne condividono un’altra. Quindi use std::mem::swap; più una tua fn swap danno lo stesso errore, ma nel «value namespace» (lo spazio dei nomi dei valori).

Pensa a una classe con due alunni che si chiamano Alex. L’insegnante non può più chiamare semplicemente «Alex». Qualcuno ha bisogno di un soprannome, come «Alex B.», ed è proprio quello che as dà a un import.

In un modulo, un nome indica esattamente un item. Quando Rust legge Error::EmptyName alla riga 10, deve sapere quale Error intendi, e si rifiuta di tirare a indovinare. Se scegliesse in silenzio il tuo enum, la tua riga use non farebbe niente; se scegliesse io::Error, il tuo enum sarebbe irraggiungibile. In entrambi i casi una scelta silenziosa nasconderebbe un errore.

Per questo E0255 è parente di E0428 (due tuoi item con lo stesso nome) e di E0252 (due righe use con lo stesso nome). La regola è la stessa: un nome, una cosa, per modulo. Vedi Moduli in Rust per capire come funziona use.

Scegli questa quando ti servono entrambi i tipi nello stesso file, cosa frequente: il tuo Error per le regole della tua app e io::Error per file e rete. Dai all’import un soprannome che dica da dove viene. IoError si legge meglio del segnaposto di rustc OtherError:

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, "quel nome è riservato"));
}
Ok(())
}
fn main() {
println!("{:?}", check_name("Ferris"));
println!("{:?}", check_name(""));
println!("{:?}", check_name("Ferris the Crab"));
match save_name("root") {
Ok(()) => println!("salvato"),
Err(e) => println!("errore di I/O: {}", e),
}
}

Output:

Ok(())
Err(EmptyName)
Err(TooLong(15))
errore di I/O: quel nome è riservato
▶ Provalo tu

Il nome "Ferris the Crab" resta in inglese perché il programma ne conta i byte con .len(): con lettere accentate il numero cambierebbe. as cambia il nome solo in questo file. Ovunque altrove il tipo resta std::io::Error.

Scegli questa quando vuoi che ogni Error mostri da dove viene a ogni uso. Importa il modulo genitore con use std::io; e scrivi io::Error. Il tuo Error tiene il nome corto. Molti programmatori Rust preferiscono questo stile per i tipi con nomi comuni come Error e 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, "nome riservato"));
}
Ok(())
}
fn main() {
if let Err(Error::EmptyName) = check_name("") {
println!("il nome è vuoto");
}
if let Err(e) = save_name("root") {
println!("errore di I/O: {}", e);
}
}

Output:

il nome è vuoto
errore di I/O: nome riservato
▶ Provalo tu

Scegli questa quando il tuo tipo verrà usato in molti file, o da altre persone. Un nome specifico come NameError o AppError è più chiaro di un secondo Error, e la riga use può restare com’è:

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("il disco è pieno");
println!("{}", disk);
}

Output:

Err(TooLong(15))
il disco è pieno
▶ Provalo tu

Nei progetti più grandi, il crate thiserror rende molto più corto scrivere enum di errore come questo. Vedi Errori personalizzati in Rust.

Scegli questa quando rustc stampa anche warning: unused import, come nell’errore qui sopra. L’import è rimasto da codice precedente, o l’ha aggiunto il tuo editor. Togli la riga, e il programma in cima a questa pagina funziona così com’è:

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!("il nome va bene"),
Err(Error::EmptyName) => println!("il nome è vuoto"),
Err(Error::TooLong(n)) => println!("il nome ha {} lettere, il massimo è 10", n),
}
}

Output:

il nome è vuoto
▶ Provalo tu

Una tua funzione con un nome della libreria standard. use std::mem::swap; più fn swap(a: &mut i32, b: &mut i32) danno E0255 con «previous import of the value swap here» e la nota «value namespace». Correzione: chiama la tua swap_values, oppure togli l’import se volevi davvero scrivere la tua.

Copia-incolla tra moduli. Sposti struct User in mod models, aggiungi use models::User; in cima, ma ti dimentichi di cancellare la vecchia struct User in main.rs. Correzione: cancella la vecchia copia, così User indica solo quella in models.

Un trait con un nome della libreria standard. use std::fmt::Display; e un tuo trait Display vanno in conflitto allo stesso modo («previous import of the trait Display»). Correzione: rinomina il tuo trait (Describe, Render), oppure scrivi use std::fmt; e fmt::Display.

Buono a sapersi: un import glob come use std::io::*; non causa mai E0255. I tuoi item vincono in silenzio sui nomi che arrivano tramite un *. È comodo, ma può anche nascondere quale Error stai usando, quindi di solito gli import espliciti sono più chiari.

  • E0252: due righe use portano lo stesso nome, come fmt::Result e io::Result.
  • E0428: due item tuoi hanno lo stesso nome, spesso perché cerchi di fare l’overloading di una funzione.
  • E0592: due metodi con lo stesso nome in un blocco impl.
  • E0432: la riga use stessa indica qualcosa che non esiste.
  • E0425: il problema opposto, un nome che non è definito affatto dove lo usi.
  • Tutti gli errori del compilatore: l’elenco completo delle pagine sugli errori.

Verifica veloce

1. Il tuo file inizia con use std::fmt::Result; e più sotto definisce enum Result { Pass, Fail }. Che errore ottieni?

2. Ti servono sia std::io::Error sia un tuo enum Error nello stesso file. Qual è una correzione pulita?

3. rustc mostra E0255 e anche warning: unused import: std::io::Error. Che cosa ti dice il warning?

Punteggio: 0 / 3