Salta ai contenuti

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.

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 module

Il 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.

rustc indica due punti:

  1. La riga --- (riga 1): «previous definition of the value area here» (qui la definizione precedente). È la prima area, quella che Rust tiene.
  2. La riga ^^^ (riga 5): «area redefined here» (qui area viene ridefinita). È la seconda, quella che viene rifiutata.
  3. La note: dice che area deve 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.

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.

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:

6
3.14159
▶ Provalo tu

La 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.

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:

6
3.14159
▶ Provalo tu

Non 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: 6
Area: 3.14159
▶ Provalo tu

È 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` here
2 |
3 | enum Error {
| ^^^^^^^^^^ `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;
| +++++++++++++

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 pieno
▶ Provalo tu

Due 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).

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.

  • 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::area la 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 use va in conflitto con un elemento locale), E0252 (due righe use in conflitto), E0592 (metodo duplicato in un impl): i fratelli visti sopra.
  • Tutti gli errori del compilatore: l’elenco completo delle pagine sugli errori.

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