Lewati ke konten

Error Rust E0034: multiple applicable items in scope (method ambigu)

E0034 artinya: tipemu punya dua method dengan nama yang sama, masing-masing dari dua trait yang berbeda, dan kamu memanggilnya dengan cara singkat, seperti book.describe(). Rust melihat keduanya dan tidak mau memilihkan salah satu untukmu. Tidak ada yang rusak pada tipe-tipemu; kamu cukup menyebutkan trait mana yang kamu maksud. Biasanya itu cuma butuh satu baris, dan rustc bahkan menuliskannya untukmu. (Halaman ini bagian dari Penjelasan error compiler Rust.)

error[E0034]: multiple applicable items in scope
--> src/main.rs:28:25
|
28 | println!("{}", book.describe()); // ❌ milik Summary atau milik Label?
| ^^^^^^^^ 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()); // ❌ milik Summary atau milik Label?
28 + println!("{}", Label::describe(&book)); // ❌ milik Summary atau milik Label?
|
help: disambiguate the method for candidate #2
|
28 - println!("{}", book.describe()); // ❌ milik Summary atau milik Label?
28 + println!("{}", Summary::describe(&book)); // ❌ milik Summary atau milik Label?
|

Artinya: ada lebih dari satu item dengan nama itu yang bisa dipakai di cakupan (scope) ini. rustc menunjukkan kedua kandidatnya, lalu cara memanggil masing-masing dengan jelas.

Ini program yang menghasilkan error tersebut:

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!("{} ({} halaman)", self.title, self.pages)
}
}
impl Label for Book {
fn describe(&self) -> String {
format!("BUKU: {}", self.title.to_uppercase())
}
}
fn main() {
let book = Book { title: String::from("Rust for Crabs"), pages: 320 };
println!("{}", book.describe()); // ❌ milik Summary atau milik Label?
}

Baca pesannya dari atas ke bawah:

  1. ^^^^^^^^ di bawah describe: “multiple describe found” (ditemukan lebih dari satu describe). Rust mencari method bernama describe pada Book dan menemukan lebih dari satu.
  2. Dua baris note: menyebutkan kandidatnya: satu berasal dari impl Label for Book (baris 21), satu lagi dari impl Summary for Book (baris 15). Keduanya nyata, keduanya bisa dipakai, dan teks yang dikembalikan berbeda.
  3. Dua baris help: menunjukkan perbaikan untuk masing-masing pilihan. Summary::describe(&book) artinya “panggil describe yang berasal dari Summary, dengan &book sebagai self”.

Bayangkan ada dua rekan kerja yang sama-sama bernama Sam di satu kantor. Berteriak “Sam!” tidak akan berhasil; kamu bilang “Sam dari bagian keuangan”. Summary::describe adalah “Sam dari bagian keuangan”.

Pemanggilan method seperti book.describe() adalah jalan pintas. Rust melihat tipe book, lalu mencari di method milik tipe itu sendiri dan di method setiap trait yang ada di cakupan dan diimplementasikan untuk Book. Biasanya pencarian itu menemukan tepat satu yang cocok. Di sini ada dua, dan keduanya melakukan hal yang berbeda: satu mencetak ringkasan, satu lagi mencetak label rak.

Rust bisa saja memilih salah satu dengan aturan tersembunyi, misalnya “trait yang didefinisikan duluan”. Tapi kalau begitu, menambahkan sebuah trait, atau menukar urutan dua baris use, akan diam-diam mengubah apa yang dicetak programmu. Rust menolak menebak dan memintamu menyebutkannya sekali, di kode, di tempat yang bisa dilihat setiap pembaca.

Ada satu pengecualian: method di blok impl Book milik tipe itu sendiri selalu menang atas method trait dengan nama yang sama, jadi kasus itu tidak pernah memberi E0034. Kamu bisa membaca lebih lanjut di Trait dan di Trait tingkat lanjut.

Perbaikan 1: Panggil lewat trait-nya: Trait::method(&x)

Section titled “Perbaikan 1: Panggil lewat trait-nya: Trait::method(&x)”

Pilih ini kalau kamu butuh kedua method itu dan hanya ingin menyebutkan yang mana setiap kali memanggil. Ini persis yang disarankan rustc. Nilainya masuk sebagai argumen pertama, dengan & yang sama seperti yang diharapkan &self milik method itu:

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!("{} ({} halaman)", self.title, self.pages)
}
}
impl Label for Book {
fn describe(&self) -> String {
format!("BUKU: {}", 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));
}

Output:

Rust for Crabs (320 halaman)
BUKU: RUST FOR CRABS
▶ Coba sendiri

Kalau method-nya menerima &mut self, kirim &mut book. Kalau menerima self, kirim book (dan nilainya berpindah, sama seperti dengan titik).

Perbaikan 2: Fully qualified syntax: <Type as Trait>::method()

Section titled “Perbaikan 2: Fully qualified syntax: <Type as Trait>::method()”

Pilih ini kalau fungsi yang bentrok tidak punya self, seperti constructor atau fn category(). Di situ tidak ada nilai yang bisa dilihat Rust, jadi Trait::category() tidak cukup: kamu juga harus menyebut tipenya. <Book as Summary>::category() dibaca “category yang dimiliki Book karena mengimplementasikan Summary”. Cara ini juga bisa dipakai untuk method:

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("Nonfiksi") }
fn describe(&self) -> String { format!("Sebuah buku berjudul {}", self.title) }
}
impl Label for Book {
fn category() -> String { String::from("Rak 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));
}

Output:

Nonfiksi
Rak B
Sebuah buku berjudul Rust for Crabs
▶ Coba sendiri

Menulis Book::category() di sini juga memberi E0034, dengan petunjuk use fully-qualified syntax to disambiguate (pakai fully qualified syntax untuk memperjelas). Menulis Summary::category() saja memberi error lain, E0790, karena trait-nya tetap tidak tahu tipe mana yang kamu maksud.

Pilih ini kalau kedua trait itu buatanmu sendiri dan nama yang sama itu kebetulan saja. Dua method yang melakukan hal berbeda layak punya nama berbeda, dan setelah itu book.describe() yang singkat bisa dipakai lagi. Sering kali ini perbaikan yang paling rapi:

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!("{} ({} halaman)", self.title, self.pages) }
}
impl Label for Book {
fn label_text(&self) -> String { format!("BUKU: {}", self.title.to_uppercase()) }
}
fn main() {
let book = Book { title: String::from("Rust for Crabs"), pages: 320 };
println!("{}", book.describe());
println!("{}", book.label_text());
}

Output:

Rust for Crabs (320 halaman)
BUKU: RUST FOR CRABS
▶ Coba sendiri

Tentu saja kamu tidak bisa mengganti nama method dari trait milik crate lain. Untuk yang seperti itu, pakai Perbaikan 1 atau Perbaikan 4.

Perbaikan 4: Import hanya trait yang kamu butuhkan

Section titled “Perbaikan 4: Import hanya trait yang kamu butuhkan”

Pilih ini kalau trait-trait itu berada di modul atau crate yang berbeda. Method sebuah trait hanya bisa dipanggil dengan titik selama trait itu ada di cakupan. Jadi kalau satu bagian kodemu hanya butuh salah satunya, import yang itu saja di sana. Kamu bahkan bisa menaruh use di dalam fungsi, supaya hanya berlaku di fungsi itu:

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!("Sebuah buku berjudul {}", self.title) }
}
impl shop::Label for Book {
fn describe(&self) -> String { format!("BUKU: {}", self.title.to_uppercase()) }
}
fn print_label(book: &Book) {
use shop::Label; // hanya Label yang ada di cakupan di sini
println!("{}", book.describe());
}
fn main() {
use report::Summary; // hanya Summary yang ada di cakupan di sini
let book = Book { title: String::from("Rust for Crabs") };
println!("{}", book.describe());
print_label(&book);
}

Output:

Sebuah buku berjudul Rust for Crabs
BUKU: RUST FOR CRABS
▶ Coba sendiri

Perhatikan bahwa blok impl menulis report::Summary dan shop::Label secara lengkap. Mengimplementasikan trait tidak butuh use; hanya pemanggilan dengan titik yang butuh. Lihat Modul untuk penjelasan lebih lanjut tentang use.

to_string() buatanmu sendiri. Setiap tipe yang mengimplementasikan Display sudah otomatis mendapat to_string() dari trait standar ToString. Tambahkan trait buatanmu dengan method to_string, dan point.to_string() memberi E0034, dengan candidate #2 is defined in an impl of the trait `ToString` for the type `T` . Perbaikannya: ganti nama method-mu (pretty(), to_label()), atau panggil Pretty::to_string(&point).

Dua import prelude::*. Library sering menyediakan use some_crate::prelude::*; untuk memasukkan trait-trait mereka. Import dua prelude yang sama-sama menambahkan method draw() atau render() ke tipe yang sama, dan pemanggilan dengan titik menjadi ambigu. Perbaikannya: ganti salah satu glob dengan item yang benar-benar kamu pakai saja, atau panggil Render::draw(&data).

Fungsi generic dengan dua bound. Di fn show<T: Summary + Label>(item: &T), item.describe() memberi E0034. Hati-hati dengan saran rustc di sini: rustc menawarkan Label::describe(&item), padahal item sudah berupa referensi, jadi hasilnya E0277 (the trait bound `&T: Label` is not satisfied). Tulis Label::describe(item) tanpa & tambahan. Lihat Generics.

  • E0790: kamu memanggil fungsi trait tanpa self, seperti Default::default() atau Summary::category(), tanpa menyebut tipenya.
  • E0599: masalah kebalikannya, tidak ada method dengan nama itu yang ditemukan. Sering kali trait-nya belum di-import.
  • E0277: trait bound tidak terpenuhi, misalnya setelah & tambahan di Trait::method(&x).
  • E0428: nama yang sama didefinisikan lebih dari sekali. Dua item dengan nama yang sama di satu modul, bukan dua method trait.
  • Semua error compiler: daftar lengkap halaman error.

Kuis singkat

1. Book mengimplementasikan Summary dan Label, dan keduanya punya fn describe(&self). Baris mana yang memanggil versi Label?

2. Kedua trait punya fn category() -> String tanpa self. Pemanggilan mana yang berhasil?

3. Book punya impl Book { fn describe(&self) ... } sendiri dan juga mengimplementasikan Summary, yang juga punya describe. Apa yang dilakukan book.describe()?

Skor: 0 / 3