Pular para o conteúdo

Erro E0428 do Rust: the name is defined multiple times (nome repetido)

O E0428 quer dizer que você definiu duas coisas com o mesmo nome no mesmo lugar: duas funções chamadas area, duas structs chamadas User, ou um enum e uma struct chamados Status. Isso acontece muito quando você tenta fazer sobrecarga (overloading) de uma função, como faria em Java, C# ou C++. O Rust não tem sobrecarga, mas tem bons substitutos, e esta página mostra quais são. Ela faz parte do guia de erros do 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 { // ❌ uma segunda `area`
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ `area` redefined here
|
= note: `area` must be defined only once in the value namespace of this module

Em português: “o nome area foi definido várias vezes”. O rustc mostra a definição anterior, a nova definição e uma nota dizendo que area só pode ser definido uma vez no namespace de valores deste módulo.

Este é o programa que gera o erro:

fn area(width: f64, height: f64) -> f64 {
width * height
}
fn area(radius: f64) -> f64 { // ❌ uma segunda `area`
3.14159 * radius * radius
}
fn main() {
println!("{}", area(2.0, 3.0));
println!("{}", area(1.0));
}

O rustc também mostra um segundo erro para este programa: o E0061, “this function takes 2 arguments but 1 argument was supplied” (esta função recebe 2 argumentos, mas foi passado 1), em area(1.0). Isso é só um efeito colateral. O Rust ficou com a primeira area e ignorou a segunda, então a chamada com um argumento não encaixa mais. Corrija o E0428 e o E0061 some junto.

O rustc aponta para dois lugares:

  1. A linha --- (linha 1): “previous definition of the value area here” (definição anterior do valor area aqui). Esta é a primeira area, a que o Rust mantém.
  2. A linha ^^^ (linha 5): “area redefined here” (area redefinida aqui). Esta é a segunda, a que é rejeitada.
  3. A note: diz que area só pode ser definida uma vez “in the value namespace of this module” (no namespace de valores deste módulo). Cada módulo é como uma gaveta, e dentro de uma gaveta cada nome precisa ser único.

Para duas structs, a nota diz “type namespace” (namespace de tipos). O Rust guarda os tipos (structs, enums, traits, módulos) e os valores (funções, constantes, statics) em duas listas separadas. É por isso que um mod shapes e uma fn shapes podem conviver lado a lado, mas duas fn area ou duas struct User não podem.

Pense em dois arquivos chamados relatorio.txt na mesma pasta. O seu computador recusa o segundo, não porque haja algo errado com ele, mas porque relatorio.txt deixaria de dizer de qual arquivo você está falando.

Em Rust, um nome num módulo aponta para exatamente um item. Quando você escreve area(...), o Rust encontra a função só pelo nome, e não pela quantidade ou pelos tipos dos argumentos. Isso deixa o código simples de ler: só existe uma area para procurar, e acrescentar uma função nova nunca muda em silêncio qual função uma chamada antiga usa.

Linguagens com sobrecarga escolhem a versão pelos argumentos, e as regras ficam complicadas quando números se convertem uns nos outros ou quando entram valores padrão. O Rust escolheu não fazer isso. Quando você quer “o mesmo nome para entradas diferentes”, usa traits ou genéricos, onde a escolha fica visível nos tipos. Mais sobre funções em Funções.

Escolha esta na maioria das vezes. Duas funções que recebem entradas diferentes normalmente fazem trabalhos diferentes, e o nome pode dizer qual. Se uma delas era uma cópia antiga que você esqueceu de apagar, apague-a:

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

Saída:

6
3.14159
▶ Experimente você mesmo

A biblioteca padrão faz o mesmo: String::new(), String::with_capacity(10) e String::from("oi") são três nomes diferentes para três jeitos de criar uma String.

Correção 2: Coloque cada uma num módulo separado

Seção intitulada “Correção 2: Coloque cada uma num módulo separado”

Escolha esta quando o nome curto é mesmo o melhor nome em cada contexto, como area para cada forma, ou um User no código do banco de dados e outro User no código da API. Cada módulo tem os seus próprios nomes, então o conflito desaparece. Quem chama escreve o nome do módulo na frente:

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

Saída:

6
3.14159
▶ Experimente você mesmo

Não esqueça o pub, senão a chamada no main dá E0603, “function is private” (a função é privada). Veja Módulos.

Escolha esta quando você quer uma chamada que funcione para vários tipos, que é para isso que a sobrecarga costuma servir. Crie um tipo para cada tipo de entrada e dê a eles um trait em comum. Agora cada forma tem a sua própria area, e o Rust escolhe a certa pelo tipo do 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 });
}

Saída:

Área: 6
Área: 3.14159
▶ Experimente você mesmo

Isso dá mais código que a Correção 1, então vale a pena quando você tem várias formas ou várias funções (area, perimeter, draw) que seguem o mesmo padrão. A função genérica print_area<T: Shape> funciona para qualquer forma que você acrescentar depois. Veja Traits e Genéricos.

Para “a mesma função com ou sem um argumento a mais”, existe uma opção mais leve: receba um Option, como em fn greet(name: &str, title: Option<&str>), e chame com None ou Some("Dr.").

Correção 4: Renomeie um import com use ... as ...

Seção intitulada “Correção 4: Renomeie um import com use ... as ...”

Escolha esta quando o conflito é entre um item seu e uma linha use. A rigor, esse é um erro irmão, o E0255, com a mesma primeira linha:

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

O rustc mostra o import anterior do tipo Error, a redefinição, e a ajuda diz “você pode usar as para mudar o nome do import”.

Dê ao import um apelido local com as. Escolha um nome que diga de onde ele vem, como IoError, em vez do OtherError provisório do 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, "disco cheio");
println!("{:?}", mine);
println!("{:?}", other);
println!("{}", disk);
}

Saída:

NotFound("config.toml")
Invalid(42)
disco cheio
▶ Experimente você mesmo

Duas linhas use que se chocam, como use std::fmt::Result; e use std::io::Result;, dão um terceiro irmão, o E0252 (`Result` reimported here, Result importado de novo aqui). A correção é a mesma: use std::io::Result as IoResult;, ou importe o módulo (use std::io;) e escreva io::Result. Renomear o seu próprio enum (AppError) também funciona.

Sobrecarregar um método num bloco impl. Duas fn new em impl Timer dão outro código, o E0592: duplicate definitions with name `new` (definições duplicadas com o nome new). Mesma causa, mesma correção: dê nomes que digam o que cada uma faz, como Timer::new() e Timer::with_seconds(30). Veja Structs.

Copiar e colar durante uma refatoração. Você copia uma função para alterá-la, ou cola uma struct User de outro arquivo, e esquece de tirar a antiga. Também é comum em testes: duas #[test] fn it_works() no mesmo mod tests dão E0428. Correção: apague a cópia antiga, ou renomeie o teste.

Variantes de enum repetidas. enum Color { Red, Red } também dá E0428, apontando para o segundo Red, com a nota “type namespace of this enum” (namespace de tipos deste enum). Correção: tire ou renomeie a variante repetida. Veja Enums.

  • E0061: número errado de argumentos. Muitas vezes aparece logo depois do E0428 quando você tentou sobrecarregar uma função.
  • E0425: o problema contrário: um nome que está definido zero vezes onde você o usa.
  • E0433: um caminho como circle::area cuja primeira parte (o módulo) não foi encontrada.
  • E0034: dois traits dão a um tipo métodos com o mesmo nome; você precisa dizer qual deles quer.
  • E0255 (um use se choca com um item local), E0252 (duas linhas use se chocam), E0592 (método repetido num impl): os irmãos mostrados acima.
  • Todos os erros do compilador: a lista completa de páginas de erro.

Teste o que aprendeu

1. Você escreve fn total(a: u32) e fn total(a: u32, b: u32) no mesmo arquivo. O que acontece?

2. mod report e fn report() podem ficar no mesmo módulo?

3. O seu arquivo tem use std::fmt::Result; e você define enum Result. Qual é uma correção limpa?

Pontuação: 0 / 3