Errore Rust E0428: the name is defined multiple times (nome duplicato)
E0428 significa che hai definito due cose con lo stesso nome nello stesso posto: due funzioni chiamate area, due struct chiamate User, oppure un enum e una struct entrambi chiamati Status. Succede spesso quando provi a fare l’overloading di una funzione, come faresti in Java, C# o C++. Rust non ha l’overloading, ma ha dei buoni sostituti, e questa pagina te li mostra. Fa parte della guida agli errori del compilatore.
Il messaggio di errore
Sezione intitolata “Il messaggio di errore”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 { // ❌ a second `area` | ^^^^^^^^^^^^^^^^^^^^^^^^^^^ `area` redefined here | = note: `area` must be defined only once in the value namespace of this moduleIl compilatore ti dice: «il nome area è definito più volte». Ti mostra dove si trova la prima definizione e dove viene ridefinito.
Ecco il programma che lo provoca:
fn area(width: f64, height: f64) -> f64 { width * height}
fn area(radius: f64) -> f64 { // ❌ a second `area` 3.14159 * radius * radius}
fn main() { println!("{}", area(2.0, 3.0)); println!("{}", area(1.0));}(Il commento dice: una seconda area.)
Per questo programma rustc stampa anche un secondo errore: E0061, «this function takes 2 arguments but 1 argument was supplied», su area(1.0). È solo un effetto collaterale. Rust ha tenuto la prima area e ha ignorato la seconda, quindi la chiamata con un solo argomento non va più bene. Risolvi E0428 e anche E0061 sparisce.
Cosa significa
Sezione intitolata “Cosa significa”rustc indica due punti:
- La riga
---(riga 1): «previous definition of the valueareahere» (qui la definizione precedente). È la primaarea, quella che Rust tiene. - La riga
^^^(riga 5): «arearedefined here» (quiareaviene ridefinita). È la seconda, quella che viene rifiutata. - La
note:dice cheareadeve essere definita una sola volta «in the value namespace of this module» (nello spazio dei nomi dei valori di questo modulo). Ogni modulo è come un cassetto, e dentro un cassetto ogni nome deve essere unico.
Per due struct la nota dice invece «type namespace». Rust tiene i tipi (struct, enum, trait, moduli) e i valori (funzioni, costanti, static) in due elenchi separati. Ecco perché un mod shapes e una fn shapes possono convivere, ma due fn area o due struct User no.
Pensa a due file chiamati report.txt nella stessa cartella. Il computer rifiuta il secondo, non perché ci sia qualcosa che non va, ma perché report.txt non direbbe più quale file intendi.
Perché Rust si comporta così
Sezione intitolata “Perché Rust si comporta così”In Rust un nome in un modulo indica esattamente un elemento. Quando scrivi area(...), Rust trova la funzione solo dal suo nome, non dal numero o dai tipi degli argomenti. Così il codice resta semplice da leggere: c’è una sola area da cercare, e aggiungere una nuova funzione non può mai cambiare in silenzio quale funzione usa una vecchia chiamata.
I linguaggi con l’overloading scelgono la versione in base agli argomenti, e le regole si complicano appena i numeri si convertono tra loro o entrano in gioco i valori predefiniti. Rust ha scelto di non farlo. Quando vuoi «lo stesso nome per input diversi», usi i trait o i generici, dove la scelta è visibile nei tipi. Trovi di più sulle funzioni in Funzioni.
Come risolverlo
Sezione intitolata “Come risolverlo”Soluzione 1: dai a ognuna il suo nome
Sezione intitolata “Soluzione 1: dai a ognuna il suo nome”Scegli questa strada quasi sempre. Due funzioni che ricevono input diversi di solito fanno lavori diversi, e il nome può dire quale. Se una delle due era una vecchia copia che ti sei dimenticato di cancellare, cancellala:
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));}Output:
63.14159La libreria standard fa lo stesso: String::new(), String::with_capacity(10) e String::from("hi") sono tre nomi diversi per tre modi di creare una String.
Soluzione 2: mettile in moduli separati
Sezione intitolata “Soluzione 2: mettile in moduli separati”Scegli questa strada quando il nome breve è davvero il nome migliore in ogni contesto, come area per ogni forma, oppure un User nel codice del database e un User diverso nel codice dell’API. Ogni modulo ha i suoi nomi, quindi il conflitto sparisce. Chi chiama scrive davanti il nome del modulo:
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));}Output:
63.14159Non dimenticare pub, altrimenti la chiamata da main dà E0603, «function is private». Vedi Moduli.
Soluzione 3: usa un trait invece dell’overloading
Sezione intitolata “Soluzione 3: usa un trait invece dell’overloading”Scegli questa strada quando vuoi una sola chiamata che funzioni per molti tipi, che di solito è lo scopo dell’overloading. Crea un tipo per ogni tipo di input, e dagli un trait in comune. Ora ogni forma ha la sua area, e Rust sceglie quella giusta dal tipo del valore:
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!("Area: {}", shape.area());}
fn main() { print_area(&Rectangle { width: 2.0, height: 3.0 }); print_area(&Circle { radius: 1.0 });}Output:
Area: 6Area: 3.14159È più codice della Soluzione 1, quindi conviene quando hai diverse forme o diverse funzioni (area, perimeter, draw) che seguono tutte lo stesso schema. La funzione generica print_area<T: Shape> funziona per qualsiasi forma aggiungerai in futuro. Vedi Trait e Generici.
Per «la stessa funzione con o senza un argomento in più» c’è un’opzione più leggera: ricevi un Option, come fn greet(name: &str, title: Option<&str>), e chiamala con None o con Some("Dott.").
Soluzione 4: rinomina un import con use ... as ...
Sezione intitolata “Soluzione 4: rinomina un import con use ... as ...”Scegli questa strada quando il conflitto è tra un tuo elemento e una riga use. A rigore questo è un errore fratello, E0255, con la stessa prima riga:
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` here2 |3 | enum Error { | ^^^^^^^^^^ `Error` redefined here | = note: `Error` must be defined only once in the type namespace of this modulehelp: you can use `as` to change the binding name of the import |1 | use std::io::Error as OtherError; | +++++++++++++Dai all’import un soprannome locale con as. Scegli un nome che dica da dove arriva, come IoError, invece del segnaposto di rustc OtherError:
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, "disco pieno");
println!("{:?}", mine); println!("{:?}", other); println!("{}", disk);}Output:
NotFound("config.toml")Invalid(42)disco pienoDue righe use in conflitto, come use std::fmt::Result; e use std::io::Result;, danno un terzo fratello, E0252 (`Result` reimported here). La soluzione è la stessa: use std::io::Result as IoResult;, oppure importa il modulo (use std::io;) e scrivi io::Result. Funziona anche rinominare il tuo enum (AppError).
Dove lo incontri di solito
Sezione intitolata “Dove lo incontri di solito”L’overloading di un metodo in un blocco impl. Due fn new in impl Timer danno un codice diverso, E0592: duplicate definitions with name `new` . Stessa causa, stessa soluzione: dai loro un nome che dica cosa fanno, come Timer::new() e Timer::with_seconds(30). Vedi Struct.
Copia e incolla durante un refactoring. Copi una funzione per modificarla, o incolli una struct User da un altro file, e ti dimentichi di togliere quella vecchia. Succede spesso anche nei test: due #[test] fn it_works() nello stesso mod tests danno E0428. Soluzione: cancella la vecchia copia, o rinomina il test.
Varianti di enum duplicate. Anche enum Color { Red, Red } dà E0428, indicando il secondo Red, con la nota «type namespace of this enum». Soluzione: togli o rinomina la variante duplicata. Vedi Enum.
Errori correlati
Sezione intitolata “Errori correlati”- E0061: numero sbagliato di argomenti. Viene spesso stampato subito dopo E0428 quando hai provato a fare l’overloading di una funzione.
- E0425: il problema opposto: un nome definito zero volte nel punto in cui lo usi.
- E0433: un percorso come
circle::areala cui prima parte (il modulo) non si trova. - E0034: due trait danno a un tipo metodi con lo stesso nome; devi dire quale intendi.
- E0255 (un
useva in conflitto con un elemento locale), E0252 (due righeusein conflitto), E0592 (metodo duplicato in unimpl): i fratelli visti sopra. - Tutti gli errori del compilatore: l’elenco completo delle pagine sugli errori.
Verifica veloce
Sezione intitolata “Verifica veloce”Verifica veloce
1. Scrivi fn total(a: u32) e fn total(a: u32, b: u32) nello stesso file. Cosa succede?
2. mod report e fn report() possono stare nello stesso modulo?
3. Il tuo file ha use std::fmt::Result; e tu definisci enum Result. Qual è una soluzione pulita?
Punteggio: 0 / 3
Per approfondire
Sezione intitolata “Per approfondire”- E0428 — Rust error codes index (in inglese): la spiegazione ufficiale, con l’esempio delle due struct.
- Defining Modules to Control Scope and Privacy — The Rust Book (in inglese): come i moduli danno a ogni parte del codice i suoi nomi.
- Providing New Names with the
asKeyword — The Rust Book (in inglese): l’esempio difmt::Resulteio::Result, risolto conas. - Traits — The Rust Book (in inglese): condividere un comportamento tra tipi, la risposta di Rust all’overloading.
- Namespaces — The Rust Reference (in inglese): le regole esatte degli spazi dei nomi dei tipi e dei valori di cui parla la nota dell’errore.