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

Ошибка Rust E0592: duplicate definitions with name X (повторное определение)

E0592 значит: у одного и того же типа в блоках impl есть два метода, две ассоциированные функции или две константы с одинаковым именем. У типа может быть сколько угодно блоков impl, но все они делят один общий список имён, так что каждое имя можно использовать только один раз. Обычно ошибка появляется после копирования кода, слияния веток или из-за второго блока impl в другом файле. Исправление — оставить одно определение или дать другому новое имя. (Эта страница входит в раздел Ошибки компилятора Rust с объяснениями.)

error[E0592]: duplicate definitions with name `reset`
--> src/main.rs:10:5
|
10 | fn reset(&mut self) {
| ^^^^^^^^^^^^^^^^^^^ duplicate definitions for `reset`
...
16 | fn reset(&mut self) { // ❌ `reset` is already defined above
| ------------------- other definition for `reset`

По-русски: «повторные определения с именем reset». rustc показывает оба места: первое отмечено ^^^, второе — --- («другое определение»).

Вот программа, которая её вызывает (комментарий // ❌ оставлен на английском, потому что rustc цитирует эту строку; он значит «reset уже определён выше»):

struct Counter {
count: u32,
}
impl Counter {
fn new() -> Self {
Counter { count: 0 }
}
fn reset(&mut self) {
self.count = 0;
}
}
impl Counter {
fn reset(&mut self) { // ❌ `reset` is already defined above
self.count = 0;
}
}
fn main() {
let mut c = Counter::new();
c.reset();
println!("{}", c.count);
}

На вызове c.reset() rustc сообщает ещё и E0034 — «multiple applicable items in scope» («в области видимости несколько подходящих элементов»), потому что не может понять, какой из двух reset ты имеешь в виду. Это ошибка-следствие: исправь E0592, и E0034 тоже исчезнет.

Заголовок называет имя, которое определено дважды (reset). ^^^ отмечает одно определение, а строка с --- — «другое определение». rustc показывает номера строк обоих, так что можно сразу к ним перейти. Если они далеко друг от друга, ... между ними значит просто «часть строк пропущена».

Неважно, что два reset лежат в разных блоках impl Counter или даже в разных файлах твоего крейта. Для Rust это всё методы одного типа, поэтому Counter::reset должно означать ровно одну функцию. То же правило действует для ассоциированных констант: два const MAX: u32 у одного типа — тоже E0592.

Это как телефонная книга, где два контакта называются «Мама». Когда ты говоришь «позвони маме», какой номер набирать?

Разносить методы по нескольким блокам impl можно, и это часто удобно: один блок для конструкторов, другой для геттеров, третий за #[cfg(test)]. Подробнее — в уроке Структуры. Но когда ты вызываешь c.reset() или Counter::reset(&mut c), Rust должен найти одну функцию. Если бы их было две, то какая из них выполнится, зависело бы от деталей, которых не видно при чтении вызова.

Поэтому Rust отказывается уже на определении, а не только на вызове. Это ловит настоящую ошибку: в скопированном методе можно исправить проблему в одной копии, а другая продолжит работать по-старому. Rust следит, чтобы исправлять всегда нужно было только одну версию.

Исправление 1: Удали дубликат и оставь одно определение

Заголовок раздела «Исправление 1: Удали дубликат и оставь одно определение»

Это нужно чаще всего. Если обе копии делают одно и то же (копирование, неаккуратное слияние, метод перенесли, но не удалили), удали одну. Перед удалением сравни тела: если в одной копии есть исправление, которого нет в другой, оставь её. Если объединить блоки impl в один, дубликаты потом будет проще заметить.

struct Counter {
count: u32,
}
impl Counter {
fn new() -> Self {
Counter { count: 0 }
}
fn increment(&mut self) {
self.count += 1;
}
fn reset(&mut self) { // ✅ у `Counter` только один `reset`
self.count = 0;
}
}
fn main() {
let mut c = Counter::new();
c.increment();
c.increment();
println!("до сброса: {}", c.count);
c.reset();
println!("после сброса: {}", c.count);
}

Вывод:

до сброса: 2
после сброса: 0
▶ Попробуй сам

Если не можешь найти второе определение, поищи по всему проекту: grep -rn "fn reset" src/. Блок impl Counter может находиться в любом модуле твоего крейта.

Выбирай этот вариант, когда два определения делают разные вещи. В Rust нет перегрузки: нельзя завести два метода с одним именем и разными параметрами, например reset() и reset(start: u32). Это тоже E0592. Дай каждому имя, которое говорит, что он делает:

struct Counter {
count: u32,
}
impl Counter {
fn new() -> Self {
Counter { count: 0 }
}
fn reset(&mut self) {
self.count = 0;
}
}
impl Counter {
fn reset_to(&mut self, start: u32) { // ✅ другое имя для другого поведения
self.count = start;
}
}
fn main() {
let mut c = Counter::new();
c.reset_to(10);
println!("после reset_to(10): {}", c.count);
c.reset();
println!("после reset(): {}", c.count);
}

Вывод:

после reset_to(10): 10
после reset(): 0
▶ Попробуй сам

Здесь помогают привычные для Rust пары имён: new и with_capacity, from_str и from_bytes, get и get_mut. Если очень хочется одно имя для разных типов аргументов, используй обобщённый параметр или трейт, например fn reset_to(&mut self, start: impl Into<u32>). Подробнее — в уроке Обобщения.

Исправление 3: Не давай обобщённым блокам impl пересекаться

Заголовок раздела «Исправление 3: Не давай обобщённым блокам impl пересекаться»

Выбирай этот вариант, когда одно из двух определений находится в обобщённом блоке вроде impl<T> Wrapper<T>. Такой блок покрывает и Wrapper<i32>, поэтому второй impl Wrapper<i32> с тем же именем метода — дубликат:

error[E0592]: duplicate definitions with name `describe`
--> src/main.rs:4:5
|
4 | fn describe(&self) -> String {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ duplicate definitions for `describe`
...
10 | fn describe(&self) -> String {
| ---------------------------- other definition for `describe`

Программа, которая к этому приводит:

struct Wrapper<T>(T);
impl<T> Wrapper<T> {
fn describe(&self) -> String {
String::from("something")
}
}
impl Wrapper<i32> {
fn describe(&self) -> String {
format!("the number {}", self.0)
}
}
fn main() {
println!("{}", Wrapper(5).describe());
}

Rust не позволяет конкретному impl переопределить обобщённый (эта возможность называется специализацией, specialization, и она пока не стабильна). Есть два хороших выхода: пиши блоки impl только для конкретных типов, чтобы они не пересекались, или вынеси поведение в трейт и реализуй его по одному разу для каждого типа:

struct Wrapper<T>(T);
// Вариант A: блоки impl для конкретных типов никогда не пересекаются
impl Wrapper<i32> {
fn describe(&self) -> String {
format!("число {}", self.0)
}
}
impl Wrapper<&str> {
fn describe(&self) -> String {
format!("текст \"{}\"", self.0)
}
}
// Вариант B: трейт, реализованный по одному разу для каждого типа
trait Label {
fn label(&self) -> String;
}
impl Label for i32 {
fn label(&self) -> String {
format!("целое {}", self)
}
}
impl Label for bool {
fn label(&self) -> String {
format!("флаг {}", self)
}
}
impl<T: Label> Wrapper<T> {
fn label(&self) -> String {
self.0.label() // один обобщённый метод, а то, что зависит от типа, живёт в трейте
}
}
fn main() {
println!("{}", Wrapper(5).describe());
println!("{}", Wrapper("hi").describe());
println!("{}", Wrapper(7).label());
println!("{}", Wrapper(true).label());
}

Вывод:

число 5
текст "hi"
целое 7
флаг true
▶ Попробуй сам

Версия с трейтом обычно лучше на будущее: чтобы поддержать новый тип, достаточно ещё одного impl Label for .... Подробнее — в уроке Трейты.

Вспомогательная функция для тестов во втором блоке impl. Ты добавляешь impl Counter { fn new() -> Self { ... } } внутри #[cfg(test)] mod tests, чтобы создавать тестовые значения, но Counter::new уже есть. Тестовый блок — всё равно impl Counter. Исправление: назови функцию, например, fn for_test() или используй настоящий new. Подробнее — в уроке Тестирование.

После слияния или рефакторинга. Две ветки обе добавили fn len(&self), или метод перенесли в новый файл, а старая копия осталась. rustc показывает оба номера строк; оставь лучшую версию.

Метод трейта с тем же именем — это нормально. impl Counter { fn reset(&mut self) } вместе с impl Resettable for Counter { fn reset(&mut self) } — не E0592: один метод принадлежит самому типу, другой — трейту, а c.reset() вызывает собственный метод типа. Два трейта с одинаковым именем метода тоже скомпилируются, но вызов c.reset() даст E0034; исправляется это через Resettable::reset(&mut c).

Проверь себя

1. У типа два блока impl. Может ли в каждом из них быть метод reset?

2. Тебе нужны area() и area(scale: f64). Что делать в Rust?

3. Почему impl<T> Wrapper<T> и impl Wrapper<i32> конфликтуют, если в обоих есть describe?

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