Ошибка Rust E0034: multiple applicable items in scope (неоднозначный метод)
E0034 значит, что у твоего типа два метода с одним и тем же именем, по одному из двух трейтов, а ты вызвал его коротким способом, например book.describe(). Rust видит оба и не станет выбирать за тебя. С твоими типами всё в порядке — нужно просто сказать, какой трейт ты имеешь в виду. Обычно это одна строка, и rustc даже пишет её за тебя. Эта страница — часть справочника по ошибкам компилятора.
Сообщение об ошибке
Заголовок раздела «Сообщение об ошибке»error[E0034]: multiple applicable items in scope --> src/main.rs:28:25 |28 | println!("{}", book.describe()); // ❌ Summary's or Label's? | ^^^^^^^^ multiple `describe` found |note: candidate #1 is defined in an impl of the trait `Label` for the type `Book` --> src/main.rs:21:5 |21 | fn describe(&self) -> String { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^note: candidate #2 is defined in an impl of the trait `Summary` for the type `Book` --> src/main.rs:15:5 |15 | fn describe(&self) -> String { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^help: disambiguate the method for candidate #1 |28 - println!("{}", book.describe()); // ❌ Summary's or Label's?28 + println!("{}", Label::describe(&book)); // ❌ Summary's or Label's? |help: disambiguate the method for candidate #2 |28 - println!("{}", book.describe()); // ❌ Summary's or Label's?28 + println!("{}", Summary::describe(&book)); // ❌ Summary's or Label's? |Компилятор говорит: в области видимости несколько подходящих элементов, найдено несколько describe, — и предлагает два способа уточнить вызов. Вот программа, которая выдаёт эту ошибку (комментарий значит «от Summary или от Label?»):
trait Summary { fn describe(&self) -> String;}
trait Label { fn describe(&self) -> String;}
struct Book { title: String, pages: u32,}
impl Summary for Book { fn describe(&self) -> String { format!("{} ({} стр.)", self.title, self.pages) }}
impl Label for Book { fn describe(&self) -> String { format!("КНИГА: {}", self.title.to_uppercase()) }}
fn main() { let book = Book { title: String::from("Rust for Crabs"), pages: 320 }; println!("{}", book.describe()); // ❌ Summary's or Label's?}Что это значит
Заголовок раздела «Что это значит»Читай сообщение сверху вниз:
^^^^^^^^подdescribe: «multipledescribefound» (найдено несколькоdescribe). Rust искал уBookметод с именемdescribeи нашёл больше одного.- Две строки
note:перечисляют кандидатов: один изimpl Label for Book(строка 21), другой изimpl Summary for Book(строка 15). Оба настоящие, оба работают, и возвращают они разный текст. - Две строки
help:показывают исправление для каждого варианта.Summary::describe(&book)значит «вызови тотdescribe, который пришёл изSummary, с&bookв ролиself».
Представь двух коллег по имени Саша в одном офисе. Крикнуть «Саша!» не получится — ты говоришь «Саша из бухгалтерии». Summary::describe — это «Саша из бухгалтерии».
Почему Rust так делает
Заголовок раздела «Почему Rust так делает»Вызов метода вроде book.describe() — это сокращение. Rust смотрит на тип book, а потом ищет среди его собственных методов и методов всех трейтов, которые в области видимости и реализованы для Book. Обычно поиск находит ровно одно совпадение. Здесь он находит два, и делают они разное: один выводит краткое описание, другой — ярлык для полки.
Rust мог бы выбрать по какому-нибудь скрытому правилу, например «трейт, объявленный первым». Но тогда добавление трейта или перестановка двух строк use тихо меняли бы то, что печатает программа. Rust отказывается гадать и просит тебя сказать один раз — в коде, где это увидит каждый читатель.
Есть одно исключение: метод из собственного блока типа impl Book всегда побеждает метод трейта с тем же именем, так что в этом случае E0034 не бывает. Подробнее — в разделах Реализуем трейт и Полный синтаксис: когда имена совпадают.
Как исправить
Заголовок раздела «Как исправить»Исправление 1: вызови через трейт: Trait::method(&x)
Заголовок раздела «Исправление 1: вызови через трейт: Trait::method(&x)»Выбирай этот вариант, когда нужны оба метода и ты просто хочешь каждый раз говорить, какой именно. Это ровно то, что предлагает rustc. Значение передаётся первым аргументом, с тем же &, которого ждёт &self у метода:
trait Summary { fn describe(&self) -> String;}
trait Label { fn describe(&self) -> String;}
struct Book { title: String, pages: u32,}
impl Summary for Book { fn describe(&self) -> String { format!("{} ({} стр.)", self.title, self.pages) }}
impl Label for Book { fn describe(&self) -> String { format!("КНИГА: {}", self.title.to_uppercase()) }}
fn main() { let book = Book { title: String::from("Rust for Crabs"), pages: 320 }; println!("{}", Summary::describe(&book)); println!("{}", Label::describe(&book));}Вывод:
Rust for Crabs (320 стр.)КНИГА: RUST FOR CRABSНазвание книги осталось английским, потому что его переводит в верхний регистр to_uppercase(). Если метод принимает &mut self, передавай &mut book. Если self — передавай book (и значение переместится, как и при вызове через точку).
Исправление 2: полный синтаксис: <Type as Trait>::method()
Заголовок раздела «Исправление 2: полный синтаксис: <Type as Trait>::method()»Выбирай этот вариант, когда у конфликтующей функции нет self, как у конструктора или fn category(). Тогда у Rust нет значения, на которое можно посмотреть, и Trait::category() недостаточно: нужно назвать ещё и тип. <Book as Summary>::category() читается как «тот category, который есть у Book, потому что он реализует Summary». Для методов это тоже работает:
trait Summary { fn category() -> String; fn describe(&self) -> String;}
trait Label { fn category() -> String;}
struct Book { title: String,}
impl Summary for Book { fn category() -> String { String::from("Научно-популярная") } fn describe(&self) -> String { format!("Книга под названием {}", self.title) }}
impl Label for Book { fn category() -> String { String::from("Полка B") }}
fn main() { let book = Book { title: String::from("Rust for Crabs") }; println!("{}", <Book as Summary>::category()); println!("{}", <Book as Label>::category()); println!("{}", <Book as Summary>::describe(&book));}Вывод:
Научно-популярнаяПолка BКнига под названием Rust for CrabsBook::category() здесь тоже даёт E0034, с подсказкой use fully-qualified syntax to disambiguate. А просто Summary::category() даёт другую ошибку, E0790, потому что трейт по-прежнему не знает, какой тип ты имеешь в виду.
Исправление 3: переименуй один из методов
Заголовок раздела «Исправление 3: переименуй один из методов»Выбирай этот вариант, когда оба трейта твои и общее имя — случайность. Два метода, которые делают разное, заслуживают разных имён, и тогда короткий book.describe() снова работает. Часто это самое чистое исправление:
trait Summary { fn describe(&self) -> String;}
trait Label { fn label_text(&self) -> String;}
struct Book { title: String, pages: u32,}
impl Summary for Book { fn describe(&self) -> String { format!("{} ({} стр.)", self.title, self.pages) }}
impl Label for Book { fn label_text(&self) -> String { format!("КНИГА: {}", self.title.to_uppercase()) }}
fn main() { let book = Book { title: String::from("Rust for Crabs"), pages: 320 }; println!("{}", book.describe()); println!("{}", book.label_text());}Вывод:
Rust for Crabs (320 стр.)КНИГА: RUST FOR CRABSМетоды трейтов из чужого крейта, конечно, не переименуешь. Для них используй исправление 1 или 4.
Исправление 4: импортируй только нужный трейт
Заголовок раздела «Исправление 4: импортируй только нужный трейт»Выбирай этот вариант, когда трейты живут в разных модулях или крейтах. Методы трейта работают через точку, только пока трейт в области видимости. Так что если какой-то части кода нужен только один из них, импортируй там только его. Можно даже написать use внутри функции — тогда он действует только в ней:
mod report { pub trait Summary { fn describe(&self) -> String; }}
mod shop { pub trait Label { fn describe(&self) -> String; }}
struct Book { title: String,}
impl report::Summary for Book { fn describe(&self) -> String { format!("Книга под названием {}", self.title) }}
impl shop::Label for Book { fn describe(&self) -> String { format!("КНИГА: {}", self.title.to_uppercase()) }}
fn print_label(book: &Book) { use shop::Label; // здесь в области видимости только Label println!("{}", book.describe());}
fn main() { use report::Summary; // здесь в области видимости только Summary let book = Book { title: String::from("Rust for Crabs") }; println!("{}", book.describe()); print_label(&book);}Вывод:
Книга под названием Rust for CrabsКНИГА: RUST FOR CRABSОбрати внимание: в блоках impl написано полностью report::Summary и shop::Label. Чтобы реализовать трейт, use не нужен — он нужен только для вызова через точку. Подробнее о use — в разделе use — сокращаем длинные пути.
Где это обычно встречается
Заголовок раздела «Где это обычно встречается»Твой собственный to_string(). Каждый тип, который реализует Display, уже получает to_string() из стандартного трейта ToString. Добавь свой трейт с методом to_string, и point.to_string() даст E0034 с candidate #2 is defined in an impl of the trait `ToString` for the type `T` . Исправление: переименуй свой метод (pretty(), to_label()) или вызывай Pretty::to_string(&point).
Два импорта prelude::*. Библиотеки часто предлагают use some_crate::prelude::*;, чтобы подключить свои трейты. Импортируй две прелюдии, которые обе добавляют одному типу метод draw() или render(), и вызов через точку станет неоднозначным. Исправление: замени один glob-импорт на только те элементы, которыми пользуешься, или вызывай Render::draw(&data).
Обобщённые функции с двумя ограничениями. В fn show<T: Summary + Label>(item: &T) вызов item.describe() даёт E0034. Осторожно с подсказкой rustc: он предлагает Label::describe(&item), но item уже ссылка, так что получится E0277 (the trait bound `&T: Label` is not satisfied). Пиши Label::describe(item) без лишнего &. Смотри урок Обобщения.
Связанные ошибки
Заголовок раздела «Связанные ошибки»- E0790: ты вызвал функцию трейта без
self, напримерDefault::default()илиSummary::category(), не указав тип. - E0599: обратная проблема — метод с таким именем не найден. Часто трейт не импортирован.
- E0277: ограничение трейта не выполнено, например из-за лишнего
&вTrait::method(&x). - E0428: имя определено несколько раз. Два элемента с одним именем в одном модуле, а не два метода трейтов.
- Все ошибки компилятора: полный список страниц об ошибках.
Проверь себя
Заголовок раздела «Проверь себя»Проверь себя
1. Book реализует Summary и Label, и в обоих есть fn describe(&self). Какая строка вызывает версию из Label?
2. В обоих трейтах есть fn category() -> String без self. Какой вызов сработает?
3. У Book есть собственный impl Book { fn describe(&self) ... }, и он реализует Summary, где тоже есть describe. Что делает book.describe()?
Результат: 0 / 3
Что почитать
Заголовок раздела «Что почитать»- E0034 — Rust error codes index (на английском): официальное объяснение с обеими полными формами записи.
- Продвинутые типажи — книга «Язык программирования Rust» (перевод на русский): раздел о полном синтаксисе с примером
Pilot,WizardиHuman. - Устранение неоднозначности в перекрывающихся трейтах — Rust на примерах (перевод на русский): короткий запускаемый пример двух трейтов с методом одного имени.
- Трейты: определение разделяемого поведения — книга «Язык программирования Rust» (перевод на русский): как объявлять и реализовывать трейты и почему трейт должен быть в области видимости, чтобы пользоваться его методами.
- Method call expressions — The Rust Reference (на английском): точные правила, по которым Rust находит метод за
x.method().