Ошибка 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): «previous definition of the valueareahere» — здесь предыдущее определение. Это перваяarea, которую Rust оставляет. - Строка
^^^(строка 5): «arearedefined here» — здесьareaопределена заново. Это вторая, и именно её Rust отвергает. - Пометка
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 так делает
Заголовок раздела «Почему Rust так делает»В Rust имя в модуле указывает ровно на один элемент. Когда ты пишешь area(...), Rust находит функцию только по имени, а не по числу или типам аргументов. Так код проще читать: искать нужно только одну area, и новая функция никогда не может тихо поменять, какую из них использует старый вызов.
Языки с перегрузкой выбирают версию по аргументам, и правила становятся запутанными, как только числа начинают преобразовываться друг в друга или добавляются значения по умолчанию. Rust решил так не делать. Когда нужно «одно имя для разных входных данных», используют трейты или обобщения, где выбор виден в типах. Подробнее о функциях — в разделе Параметры: даём функции входные данные.
Как исправить
Заголовок раздела «Как исправить»Исправление 1: дай каждой своё имя
Заголовок раздела «Исправление 1: дай каждой своё имя»Выбирай этот вариант в большинстве случаев. Две функции, которые принимают разные данные, обычно делают разную работу, и имя может это сказать. Если одна из них — старая копия, которую ты забыл удалить, просто удали её:
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));}Вывод:
63.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));}Вывод:
63.14159Не забудь pub, иначе вызов из main даст E0603, «function is private». Смотри Объявляем модуль.
Исправление 3: трейт вместо перегрузки
Заголовок раздела «Исправление 3: трейт вместо перегрузки»Выбирай этот вариант, когда нужен один вызов, который работает для многих типов — обычно именно для этого и нужна перегрузка. Сделай тип для каждого вида входных данных и дай им общий трейт. Теперь у каждой фигуры своя 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` 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; | +++++++++++++Дай импорту локальное прозвище через 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
Что почитать
Заголовок раздела «Что почитать»- E0428 — Rust error codes index (на английском): официальное объяснение с примером двух структур.
- Определение модулей для управления областью действия и конфиденциальностью — книга «Язык программирования Rust» (перевод на русский): как модули дают каждой части кода свои имена.
- Предоставление новых имён с помощью ключевого слова as — книга «Язык программирования Rust» (перевод на русский): пример с
fmt::Resultиio::Result, решённый черезas. - Трейты: определение разделяемого поведения — книга «Язык программирования Rust» (перевод на русский): общее поведение для разных типов — ответ Rust на перегрузку.
- Namespaces — The Rust Reference (на английском): точные правила для пространств имён типов и значений, о которых говорит пометка в ошибке.