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.
El mensaje de error
Sección titulada «El mensaje de error»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.
Qué significa
Sección titulada «Qué significa»rustc señala dos sitios:
- La línea
---(línea 1): “previous definition of the valueareahere” (aquí está la definición anterior del valorarea). Es el primerarea, el que Rust conserva. - La línea
^^^(línea 5): “arearedefined here” (aquí se vuelve a definirarea). Es el segundo, y es el que se rechaza. - La
note:dice queareatiene 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.
Por qué Rust hace esto
Sección titulada «Por qué Rust hace esto»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.
Cómo solucionarlo
Sección titulada «Cómo solucionarlo»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:
63.14159La 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.
Solución 2: Ponlas en módulos separados
Sección titulada «Solución 2: Ponlas en módulos separados»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:
63.14159No 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.14159Es 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` 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; | +++++++++++++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á llenoDos 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.
Dónde lo verás normalmente
Sección titulada «Dónde lo verás normalmente»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.
Errores relacionados
Sección titulada «Errores relacionados»- 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::areacuya 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
usechoca con un elemento local), E0252 (dos líneasusechocan), E0592 (método duplicado en unimpl): los hermanos que viste arriba. - Todos los errores del compilador: la lista completa de páginas de errores.
Comprueba lo que aprendiste
Sección titulada «Comprueba lo que aprendiste»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
Para leer más
Sección titulada «Para leer más»- E0428 — Rust error codes index (en inglés): la explicación oficial, con el ejemplo de los dos structs.
- Defining Modules to Control Scope and Privacy — El libro de Rust (en inglés): cómo los módulos le dan a cada parte de tu código sus propios nombres. También existe una traducción al español de la comunidad del libro.
- Providing New Names with the
asKeyword — El libro de Rust (en inglés): el ejemplo defmt::Resulteio::Result, resuelto conas. - Traits — El libro de Rust (en inglés): compartir comportamiento entre tipos, la respuesta de Rust a la sobrecarga.
- Namespaces — The Rust Reference (en inglés): las reglas exactas de los espacios de nombres de tipos y de valores que menciona la nota del error.