Перейти к содержимому

Ошибка Rust E0428: the name is defined multiple times (повторное имя)

E0428 значит, что ты объявил две вещи с одним и тем же именем в одном и том же месте: две функции area, две структуры User или перечисление и структуру, обе с именем Status. Часто так бывает, когда пытаешься перегрузить (overload) функцию, как в Java, C# или C++. Перегрузки в Rust нет, зато есть хорошие замены, и эта страница их показывает. Она входит в справочник по ошибкам компилятора.

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

Компилятор говорит: имя area определено несколько раз. Вот программа, которая выдаёт эту ошибку (комментарий значит «вторая area»):

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 выводит и вторую ошибку: E0061, «this function takes 2 arguments but 1 argument was supplied» (функция принимает 2 аргумента, а передан 1), на area(1.0). Это лишь побочный эффект. Rust оставил первую area и проигнорировал вторую, поэтому вызов с одним аргументом больше не подходит. Исправь E0428 — и E0061 уйдёт вместе с ней.

rustc показывает на два места:

  1. Строка --- (строка 1): «previous definition of the value area here» — здесь предыдущее определение. Это первая area, которую Rust оставляет.
  2. Строка ^^^ (строка 5): «area redefined here» — здесь area определена заново. Это вторая, и именно её Rust отвергает.
  3. Пометка note: говорит, что area должна быть определена только один раз «in the value namespace of this module» — в пространстве имён значений этого модуля. Каждый модуль — как один ящик стола, и внутри ящика каждое имя должно быть уникальным.

Для двух структур в пометке будет «type namespace» (пространство имён типов). Rust держит типы (структуры, перечисления, трейты, модули) и значения (функции, константы, статические переменные) в двух разных списках. Поэтому mod shapes и fn shapes могут жить рядом, а две fn area или две struct User — нет.

Представь два файла report.txt в одной папке. Компьютер не даст сохранить второй — не потому, что с ним что-то не так, а потому, что имя report.txt перестало бы указывать, какой файл ты имеешь в виду.

В Rust имя в модуле указывает ровно на один элемент. Когда ты пишешь area(...), Rust находит функцию только по имени, а не по числу или типам аргументов. Так код проще читать: искать нужно только одну area, и новая функция никогда не может тихо поменять, какую из них использует старый вызов.

Языки с перегрузкой выбирают версию по аргументам, и правила становятся запутанными, как только числа начинают преобразовываться друг в друга или добавляются значения по умолчанию. Rust решил так не делать. Когда нужно «одно имя для разных входных данных», используют трейты или обобщения, где выбор виден в типах. Подробнее о функциях — в разделе Параметры: даём функции входные данные.

Выбирай этот вариант в большинстве случаев. Две функции, которые принимают разные данные, обычно делают разную работу, и имя может это сказать. Если одна из них — старая копия, которую ты забыл удалить, просто удали её:

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

Вывод:

6
3.14159
▶ Попробуй сам

Стандартная библиотека делает так же: String::new(), String::with_capacity(10) и String::from("hi") — три разных имени для трёх способов создать String.

Исправление 2: разложи их по разным модулям

Заголовок раздела «Исправление 2: разложи их по разным модулям»

Выбирай этот вариант, когда короткое имя действительно лучшее в каждом контексте, например area для каждой фигуры или User в коде базы данных и другой User в коде API. У каждого модуля свои имена, так что конфликт исчезает. Вызывающий код пишет имя модуля впереди:

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

Вывод:

6
3.14159
▶ Попробуй сам

Не забудь pub, иначе вызов из main даст E0603, «function is private». Смотри Объявляем модуль.

Выбирай этот вариант, когда нужен один вызов, который работает для многих типов — обычно именно для этого и нужна перегрузка. Сделай тип для каждого вида входных данных и дай им общий трейт. Теперь у каждой фигуры своя area, и Rust выбирает нужную по типу значения:

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!("Площадь: {}", shape.area());
}
fn main() {
print_area(&Rectangle { width: 2.0, height: 3.0 });
print_area(&Circle { radius: 1.0 });
}

Вывод:

Площадь: 6
Площадь: 3.14159
▶ Попробуй сам

Кода здесь больше, чем в исправлении 1, так что это окупается, когда фигур несколько или несколько функций (area, perimeter, draw) устроены по одному образцу. Обобщённая print_area<T: Shape> работает для любой фигуры, которую ты добавишь потом. Смотри Трейты в параметрах функций и урок Обобщения.

Для случая «та же функция с дополнительным аргументом или без него» есть вариант полегче: принимай Option, например fn greet(name: &str, title: Option<&str>), и вызывай с None или Some("Dr.").

Исправление 4: переименуй импорт через use ... as ...

Заголовок раздела «Исправление 4: переименуй импорт через use ... as ...»

Выбирай этот вариант, когда конфликт — между твоим собственным элементом и строкой use. Строго говоря, это родственная ошибка, E0255, с тем же заголовком:

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

Дай импорту локальное прозвище через as. Выбери имя, которое говорит, откуда он пришёл, например IoError, а не заглушку OtherError из подсказки 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, "диск заполнен");
println!("{:?}", mine);
println!("{:?}", other);
println!("{}", disk);
}

Вывод:

NotFound("config.toml")
Invalid(42)
диск заполнен
▶ Попробуй сам

Две конфликтующие строки use, например use std::fmt::Result; и use std::io::Result;, дают третью родственную ошибку, E0252 (`Result` reimported here). Исправление то же: use std::io::Result as IoResult; или импортируй модуль (use std::io;) и пиши io::Result. Переименовать своё перечисление (AppError) тоже можно.

Перегрузка метода в блоке impl. Две fn new в impl Timer дают другой код, E0592: duplicate definitions with name `new` . Причина та же, исправление то же: назови их по тому, что они делают, например Timer::new() и Timer::with_seconds(30). Смотри урок Структуры.

Копирование при рефакторинге. Ты копируешь функцию, чтобы её поменять, или вставляешь struct User из другого файла и забываешь удалить старую версию. Часто бывает и в тестах: два #[test] fn it_works() в одном mod tests дают E0428. Исправление: удали старую копию или переименуй тест.

Повторяющиеся варианты перечисления. enum Color { Red, Red } тоже даёт E0428 с указанием на второй Red и пометкой «type namespace of this enum». Исправление: удали или переименуй повторяющийся вариант. Смотри урок Перечисления.

  • E0061: неверное число аргументов. Часто выводится сразу после E0428, когда ты пытался перегрузить функцию.
  • E0425: обратная проблема — имя, которое там, где ты его используешь, определено ноль раз.
  • E0433: путь вроде circle::area, первую часть которого (модуль) не удаётся найти.
  • E0034: два трейта дают типу методы с одинаковым именем; нужно сказать, какой из них ты имеешь в виду.
  • E0255 (use конфликтует с локальным элементом), E0252 (конфликтуют две строки use), E0592 (повторяющийся метод в impl): родственные ошибки, показанные выше.
  • Все ошибки компилятора: полный список страниц об ошибках.

Проверь себя

1. Ты пишешь fn total(a: u32) и fn total(a: u32, b: u32) в одном файле. Что произойдёт?

2. Могут ли mod report и fn report() жить в одном модуле?

3. В твоём файле есть use std::fmt::Result;, и ты объявляешь enum Result. Какое исправление аккуратное?

Результат: 0 / 3