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

Ошибка 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?
}

Читай сообщение сверху вниз:

  1. ^^^^^^^^ под describe: «multiple describe found» (найдено несколько describe). Rust искал у Book метод с именем describe и нашёл больше одного.
  2. Две строки note: перечисляют кандидатов: один из impl Label for Book (строка 21), другой из impl Summary for Book (строка 15). Оба настоящие, оба работают, и возвращают они разный текст.
  3. Две строки help: показывают исправление для каждого варианта. Summary::describe(&book) значит «вызови тот describe, который пришёл из Summary, с &book в роли self».

Представь двух коллег по имени Саша в одном офисе. Крикнуть «Саша!» не получится — ты говоришь «Саша из бухгалтерии». Summary::describe — это «Саша из бухгалтерии».

Вызов метода вроде 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 Crabs
▶ Попробуй сам

Book::category() здесь тоже даёт E0034, с подсказкой use fully-qualified syntax to disambiguate. А просто Summary::category() даёт другую ошибку, E0790, потому что трейт по-прежнему не знает, какой тип ты имеешь в виду.

Выбирай этот вариант, когда оба трейта твои и общее имя — случайность. Два метода, которые делают разное, заслуживают разных имён, и тогда короткий 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