Ir al contenido

Error E0428 de Rust: the name is defined multiple times (nombre duplicado)

E0428 significa que definiste dos cosas con el mismo nombre en el mismo sitio: dos funciones llamadas area, dos structs llamados User, o un enum y un struct que se llaman los dos Status. A menudo pasa cuando intentas sobrecargar (overload) una función, como harías en Java, C# o C++. Rust no tiene sobrecarga, pero tiene buenas alternativas, y esta página te las enseña. Forma parte de la guía de errores del compilador.

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

“The name area is defined multiple times” quiere decir “el nombre area está definido varias veces”. Abajo te explicamos cada una de las etiquetas.

Este es el programa que lo produce:

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));
}

rustc también muestra un segundo error con este programa: E0061, “this function takes 2 arguments but 1 argument was supplied” (esta función recibe 2 argumentos pero se le pasó 1), en area(1.0). Es solo un efecto secundario. Rust se quedó con el primer area e ignoró el segundo, así que la llamada con un solo argumento ya no encaja. Arregla E0428 y E0061 desaparece con él.

rustc señala dos sitios:

  1. La línea --- (línea 1): “previous definition of the value area here” (aquí está la definición anterior del valor area). Es el primer area, el que Rust conserva.
  2. La línea ^^^ (línea 5): “area redefined here” (aquí se vuelve a definir area). Es el segundo, y es el que se rechaza.
  3. La note: dice que area tiene que definirse una sola vez “in the value namespace of this module” (en el espacio de nombres de valores de este módulo). Cada módulo es como un cajón, y dentro de un cajón cada nombre tiene que ser único.

Con dos structs, la nota dice “type namespace” (espacio de nombres de tipos). Rust guarda los tipos (structs, enums, traits, módulos) y los valores (funciones, constantes, statics) en dos listas separadas. Por eso un mod shapes y un fn shapes pueden convivir, pero dos fn area o dos struct User no.

Piensa en dos archivos llamados informe.txt en la misma carpeta. Tu ordenador rechaza el segundo, no porque tenga algo malo, sino porque informe.txt ya no le diría a qué archivo te refieres.

En Rust, un nombre dentro de un módulo apunta a exactamente un elemento. Cuando escribes area(...), Rust encuentra la función solo por su nombre, no por el número o los tipos de los argumentos. Así el código es fácil de leer: solo hay un area que buscar, y añadir una función nueva nunca puede cambiar sin avisar a cuál llama una llamada antigua.

Los lenguajes con sobrecarga eligen la versión según los argumentos, y las reglas se complican en cuanto los números se convierten entre sí o entran en juego los valores por defecto. Rust decidió no hacerlo. Cuando quieres “el mismo nombre para entradas distintas”, usas traits o genéricos, donde la elección se ve en los tipos. Más sobre funciones en Parámetros: darle datos a una función.

Solución 1: Dale a cada una su propio nombre

Sección titulada «Solución 1: Dale a cada una su propio nombre»

Elige esta la mayoría de las veces. Dos funciones que reciben entradas distintas normalmente hacen trabajos distintos, y el nombre puede decir cuál. Si una de ellas era una copia antigua que olvidaste borrar, bórrala:

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));
}

Salida:

6
3.14159
▶ Pruébalo tú mismo

La biblioteca estándar hace lo mismo: String::new(), String::with_capacity(10) y String::from("hola") son tres nombres distintos para tres formas de crear un String.

Elige esta cuando el nombre corto de verdad es el mejor en cada contexto, como area para cada figura, o un User en tu código de base de datos y otro User distinto en tu código de la API. Cada módulo tiene sus propios nombres, así que el choque desaparece. Quien llama escribe el nombre del módulo delante:

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));
}

Salida:

6
3.14159
▶ Pruébalo tú mismo

No olvides el pub, o la llamada desde main da E0603, “function is private” (la función es privada). Mira Definir un módulo.

Solución 3: Usa un trait en lugar de sobrecarga

Sección titulada «Solución 3: Usa un trait en lugar de sobrecarga»

Elige esta cuando quieres una sola llamada que funcione con muchos tipos, que es para lo que suele servir la sobrecarga. Crea un tipo para cada clase de entrada y dales un trait en común. Ahora cada figura tiene su propio area, y Rust elige el correcto según el tipo del valor:

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!("Área: {}", shape.area());
}
fn main() {
print_area(&Rectangle { width: 2.0, height: 3.0 });
print_area(&Circle { radius: 1.0 });
}

Salida:

Área: 6
Área: 3.14159
▶ Pruébalo tú mismo

Es más código que la Solución 1, así que compensa cuando tienes varias figuras o varias funciones (area, perimeter, draw) que siguen el mismo patrón. La función genérica print_area<T: Shape> funciona con cualquier figura que añadas más adelante. Mira Traits como parámetros de funciones y Genéricos.

Para “la misma función con o sin un argumento extra”, hay una opción más ligera: recibe un Option, como fn greet(name: &str, title: Option<&str>), y llámala con None o con Some("Dra.").

Solución 4: Renombra un import con use ... as ...

Sección titulada «Solución 4: Renombra un import con use ... as ...»

Elige esta cuando el choque es entre un elemento tuyo y una línea use. Para ser exactos, es un error hermano, E0255, con el mismo titular:

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;
| +++++++++++++

La primera etiqueta señala el import anterior del tipo Error, la segunda dice que Error se vuelve a definir en la línea 3, y el help te sugiere usar as para cambiar el nombre con el que se importa. Dale al import un apodo local con as. Elige un nombre que diga de dónde viene, como IoError, en lugar del OtherError provisional de rustc:

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, "el disco está lleno");
println!("{:?}", mine);
println!("{:?}", other);
println!("{}", disk);
}

Salida:

NotFound("config.toml")
Invalid(42)
el disco está lleno
▶ Pruébalo tú mismo

Dos líneas use que chocan, como use std::fmt::Result; y use std::io::Result;, dan un tercer hermano, E0252 (`Result` reimported here, Result se vuelve a importar aquí). La solución es la misma: use std::io::Result as IoResult;, o importa el módulo (use std::io;) y escribe io::Result. Cambiar el nombre de tu propio enum (AppError) también funciona.

Al sobrecargar un método en un bloque impl. Dos fn new en impl Timer dan otro código, E0592: duplicate definitions with name `new` (definiciones duplicadas con el nombre new). La misma causa y la misma solución: ponles nombres según lo que hacen, como Timer::new() y Timer::with_seconds(30). Mira Structs.

Copiar y pegar al refactorizar. Copias una función para cambiarla, o pegas un struct User de otro archivo, y te olvidas de quitar el antiguo. También es habitual en los tests: dos #[test] fn it_works() en un mismo mod tests dan E0428. Solución: borra la copia antigua o cambia el nombre del test.

Variantes de enum duplicadas. enum Color { Red, Red } también da E0428, señalando el segundo Red, con la nota “type namespace of this enum” (espacio de nombres de tipos de este enum). Solución: quita o renombra la variante duplicada. Mira Enums.

  • E0061: número de argumentos equivocado. A menudo aparece justo después de E0428 cuando intentaste sobrecargar una función.
  • E0425: el problema contrario: un nombre que está definido cero veces donde lo usas.
  • E0433: una ruta como circle::area cuya primera parte (el módulo) no se encuentra.
  • E0034: dos traits le dan a un tipo métodos con el mismo nombre; tienes que decir cuál quieres.
  • E0255 (un use choca con un elemento local), E0252 (dos líneas use chocan), E0592 (método duplicado en un impl): los hermanos que viste arriba.
  • Todos los errores del compilador: la lista completa de páginas de errores.

Comprueba lo aprendido

1. Escribes fn total(a: u32) y fn total(a: u32, b: u32) en el mismo archivo. ¿Qué pasa?

2. ¿Pueden convivir mod report y fn report() en el mismo módulo?

3. Tu archivo tiene use std::fmt::Result; y defines enum Result. ¿Cuál es una solución limpia?

Puntuación: 0 / 3