Lewati ke konten

Error Rust E0790: cannot call associated function on trait (tipe tidak disebut)

E0790 artinya kamu meminta sesuatu dari sebuah trait yang sebenarnya hanya bisa diberikan oleh sebuah tipe. Default::default() saja itu seperti bilang “berikan aku nilai default-nya” tanpa menyebut default apa. Error ini cukup sering muncul, dan perbaikannya biasanya cukup menaruh nama tipenya di depan: Settings::default(), bukan Default::default(). Halaman ini menunjukkan perbaikannya dulu, lalu menjelaskan beda antara trait dan tipe, supaya error ini tidak lagi mengejutkanmu. Halaman ini bagian dari Penjelasan error compiler Rust.

error[E0790]: cannot call associated function on trait without specifying the corresponding `impl` type
--> src/main.rs:8:20
|
8 | let settings = Default::default(); // ❌ default dari tipe apa?
| ^^^^^^^^^^^^^^^^^^ cannot call associated function of trait
|
help: use a fully-qualified path to a specific available implementation
|
8 | let settings = </* self type */ as Default>::default(); // ❌ default dari tipe apa?
| +++++++++++++++++++ +

Compiler bilang associated function sebuah trait tidak bisa dipanggil tanpa menyebut tipe impl-nya, lalu menyarankan path lengkap dengan tempat kosong untuk tipenya.

Ini program yang menghasilkan error tersebut:

#[derive(Debug, Default)]
struct Settings {
volume: u8,
dark_mode: bool,
}
fn main() {
let settings = Default::default(); // ❌ default dari tipe apa?
println!("{:?}", settings);
}

Kamu mendapat error yang sama kalau memanggil fungsi dari trait buatanmu sendiri lewat nama trait-nya, seperti Shape::new(). Di sini rustc melihat dua tipe yang mengimplementasikan Shape, jadi keduanya ditampilkan sebagai pilihan:

error[E0790]: cannot call associated function on trait without specifying the corresponding `impl` type
--> src/main.rs:33:17
|
2 | fn new() -> Self;
| ----------------- `Shape::new` defined here
...
33 | let shape = Shape::new();
| ^^^^^^^^^^^^ cannot call associated function of trait
|
help: use a fully-qualified path to one of the available implementations
|
33 | let shape = <Circle as Shape>::new();
| ++++++++++ +
33 | let shape = <Square as Shape>::new();
| ++++++++++ +

Bagian help: menawarkan dua path lengkap: satu untuk Circle dan satu untuk Square.

Untuk konstanta trait seperti Plugin::ID, kalimat utamanya sedikit berubah: cannot refer to the associated constant on trait without specifying the corresponding `impl` type (tidak bisa merujuk konstanta trait tanpa menyebut tipe impl-nya). Masalahnya sama, perbaikannya juga sama.

Lihat apa yang ditandai rustc:

  1. ^^^^ di bawah Default::default(): “cannot call associated function of trait” (tidak bisa memanggil associated function milik trait). default adalah fungsi milik trait Default, dan kamu memanggilnya lewat nama trait-nya.
  2. Bagian help menyarankan </* self type */ as Default>::default(). Bagian /* self type */ adalah tempat kosong yang harus kamu isi. rustc bertanya: “default milik tipe yang mana yang kamu maksud?”
  3. Di contoh Shape, rustc mengisi tempat kosong itu untukmu dengan setiap tipe yang ia temukan (Circle, Square). Kalau hanya satu tipe yang mengimplementasikan trait-nya, ia hanya menyarankan tipe itu.

Ini ide kuncinya. Trait adalah janji: “setiap tipe yang mengimplementasikan Default bisa membuat nilai awal dari dirinya sendiri”. Tipe adalah yang menepati janji itu: Settings, String, u32, dan Vec<T> masing-masing menepatinya dengan caranya sendiri. Default::default() meminta nilai dari janjinya itu sendiri, padahal janji tidak bisa membuat apa-apa. Hanya tipe yang bisa.

Bayangkan tulisan “Bisa dibungkus” yang dipasang di jendela banyak restoran. Kamu tidak bisa memesan makan malam dari tulisan itu; kamu harus memilih restorannya. Settings::default() berarti memilih restorannya.

Kebanyakan method trait menerima self, seperti circle.area(). Method seperti itu tidak pernah menyebabkan E0790, karena nilainya sudah memberi tahu Rust tipenya: circle adalah Circle, jadi yang dipanggil adalah area milik Circle. Selengkapnya ada di Trait.

Sebagian fungsi trait tidak punya self: constructor seperti new() dan default(), konversi seperti From::from, dan konstanta trait seperti ID. Tanpa nilai yang bisa dilihat, Rust hanya punya dua tempat untuk mengetahui tipenya: nama yang kamu tulis di depan (Settings::), atau tipe yang harus dimiliki hasilnya (let s: Settings = ...). Kalau dua-duanya tidak ada, Rust tidak bisa menebak. Banyak tipe mengimplementasikan Default, dan memilih salah satunya secara acak akan diam-diam mengubah perilaku programmu.

Ini sama seperti interface di Java atau C#. Kamu tidak bisa memanggil new pada sebuah interface; kamu butuh class yang mengimplementasikannya. Rust hanya memberitahumu soal ini saat compile, lengkap dengan tempat persis yang perlu diperbaiki.

Pilih ini hampir selalu. Ganti nama trait-nya dengan nama tipenya. Ini perbaikan yang paling singkat, dan mudah dibaca: “Settings default”, “Circle baru”:

#[derive(Debug, Default)]
struct Settings {
volume: u8,
dark_mode: bool,
}
trait Shape {
fn new() -> Self;
fn name() -> String;
}
struct Circle {
radius: f64,
}
impl Shape for Circle {
fn new() -> Self {
Circle { radius: 1.0 }
}
fn name() -> String {
String::from("lingkaran")
}
}
fn main() {
let settings = Settings::default(); // bukan Default::default()
let shape = Circle::new(); // bukan Shape::new()
println!("{:?}", settings);
println!("Sebuah {} dengan jari-jari {}", Circle::name(), shape.radius);
}

Output:

Settings { volume: 0, dark_mode: false }
Sebuah lingkaran dengan jari-jari 1
▶ Coba sendiri

Circle::new() bisa dipakai walaupun new didefinisikan di trait, bukan di blok impl Circle. Rust mencari new pada Circle, menemukannya lewat impl Shape for Circle, lalu memanggilnya. Trait-nya harus ada di scope (didefinisikan di file yang sama, atau dibawa masuk dengan use).

Perbaikan 2: Beri anotasi pada variabelnya

Section titled “Perbaikan 2: Beri anotasi pada variabelnya”

Pilih ini kalau kamu suka gaya Default::default(), atau nama tipenya panjang. Tulis tipenya pada variabel, dan dari situ Rust tahu default mana yang harus dipanggil:

#[derive(Debug, Default)]
struct Settings {
volume: u8,
dark_mode: bool,
}
fn main() {
let settings: Settings = Default::default();
let small: u8 = 42;
let big: u32 = From::from(small);
println!("{:?}", settings);
println!("{}", big);
}

Output:

Settings { volume: 0, dark_mode: false }
42
▶ Coba sendiri

Cara ini hanya bisa dipakai kalau fungsinya mengembalikan tipenya sendiri (Self), seperti default(), new(), dan from(). Cara ini tidak bisa untuk fn name() -> String: tahu bahwa hasilnya String tidak memberi tahu Rust shape mana yang kamu maksud. Untuk kasus itu, pakai Perbaikan 1 atau Perbaikan 3. Selengkapnya tentang anotasi di Tipe data.

Perbaikan 3: Fully qualified syntax <Type as Trait>::function()

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

Inilah bentuk “sebutkan semuanya” yang disarankan rustc. Baca <Circle as Shape>::new() sebagai “new yang dimiliki Circle karena ia mengimplementasikan Shape”. Bentuk ini selalu berhasil, tapi Circle::new() lebih mudah dibaca, jadi kamu hanya perlu memakainya kalau bentuk pendeknya ambigu:

  • tipenya mengimplementasikan dua trait yang punya fungsi bernama sama, atau
  • tipenya punya fungsinya sendiri dengan nama itu, dan kamu ingin versi milik trait.

Di sini sebuah Sale bisa ditulis sebagai baris CSV atau sebagai laporan cetak, dan kedua trait punya header(). Menulis Sale::header() memberi E0034 (bahasa Inggris) “multiple applicable items in scope” (ada beberapa item yang cocok di scope), jadi kamu harus menyebut yang mana:

trait Csv {
fn header() -> String;
}
trait Report {
fn header() -> String;
}
struct Sale;
impl Csv for Sale {
fn header() -> String { String::from("tanggal,jumlah") }
}
impl Report for Sale {
fn header() -> String { String::from("Penjualan bulanan") }
}
fn main() {
println!("{}", <Sale as Csv>::header());
println!("{}", <Sale as Report>::header());
}

Output:

tanggal,jumlah
Penjualan bulanan
▶ Coba sendiri

Pelajaran Trait lanjutan punya contoh lain, termasuk method yang menerima &self.

Perbaikan 4: Di kode generic, pakai T:: dan turbofish

Section titled “Perbaikan 4: Di kode generic, pakai T:: dan turbofish”

Pilih ini kalau kamu sedang menulis fungsi generic. Di dalam fn register<T: Plugin>(), tipenya adalah T, jadi panggil T::name() dan T::ID, bukan Plugin::name(). Saat memanggil fungsinya, berikan tipenya dengan turbofish ::<Type>, karena tidak ada argumen yang bisa dipakai Rust untuk mengetahuinya:

trait Plugin {
const ID: u32;
fn name() -> String;
}
struct Logger;
struct Backup;
impl Plugin for Logger {
const ID: u32 = 7;
fn name() -> String { String::from("logger") }
}
impl Plugin for Backup {
const ID: u32 = 12;
fn name() -> String { String::from("backup") }
}
fn register<T: Plugin>() -> String {
format!("Plugin #{}: {}", T::ID, T::name())
}
fn main() {
println!("{}", register::<Logger>());
println!("{}", register::<Backup>());
}

Output:

Plugin #7: logger
Plugin #12: backup
▶ Coba sendiri

Kalau kamu tidak menulis turbofish-nya dan hanya menulis register(), kamu mendapat E0283: “cannot infer type of the type parameter T” (tipe untuk parameter T tidak bisa disimpulkan). T juga sebuah tipe di sini, jadi T:: bekerja sama seperti Circle:: di Perbaikan 1. Lihat Generics untuk penjelasan lebih lanjut tentang parameter tipe.

Default::default() tanpa petunjuk apa pun. let config = Default::default(); gagal, tapi pemanggilan yang sama berhasil di mana pun tipenya sudah diketahui: di field struct, di argumen fungsi, atau di sintaks “isi sisanya”:

let settings = Settings { volume: 8, ..Default::default() }; // OK: Rust tahu ini Settings

Perbaiki baris yang gagal dengan Settings::default() atau let config: Settings = Default::default();. Lihat Trait yang sering dipakai.

Di dalam fungsi generic. Menulis nama trait-nya, bukan T:

error[E0790]: cannot call associated function on trait without specifying the corresponding `impl` type
--> src/main.rs:9:29
|
2 | fn name() -> String;
| -------------------- `Shape::name` defined here
...
9 | format!("This is a {}", Shape::name())
| ^^^^^^^^^^^^^ cannot call associated function of trait
|
help: use the fully-qualified path to the only available implementation
|
9 | format!("This is a {}", <Circle as Shape>::name())
| ++++++++++ +

rustc menyarankan path lengkap ke satu-satunya implementasi yang tersedia.

Hati-hati: rustc menyarankan <Circle as Shape> karena sejauh ini Circle adalah satu-satunya shape. Kode itu memang bisa di-compile, tapi fungsinya akan selalu memakai nama milik Circle, apa pun T-nya. Perbaikan yang benar adalah T::name(), seperti di Perbaikan 4.

Konstanta trait. println!("{}", Plugin::ID); meminta sebuah angka dari janjinya, padahal hanya setiap plugin yang punya angka itu. Perbaikannya: Logger::ID, atau T::ID di kode generic.

From::from(x) tanpa tujuan. let total = From::from(42u8); memberi E0790, karena banyak tipe bisa dibuat dari u8. Perbaikannya: u32::from(42u8), atau let total: u32 = 42u8.into();. Lihat Konversi.

  • E0282: type annotations needed (anotasi tipe dibutuhkan). Kalau kamu memakai nilainya nanti, seperti let c = Shape::new(); lalu c.area(), rustc melaporkan E0282 sebagai gantinya. Perbaikannya sama.
  • E0283: anotasi tipe dibutuhkan saat banyak tipe cocok, misalnya Into::into(n) tanpa tujuan, atau fungsi generic yang dipanggil tanpa turbofish.
  • E0599: fungsinya tidak ditemukan. Settings::default() memberi error ini kalau Settings tidak mengimplementasikan Default; tambahkan #[derive(Default)]. Dengan let s: Settings = Default::default();, kesalahan yang sama muncul sebagai E0277.
  • E0424: self dipakai di fungsi yang tidak punya parameter self. Ini juga tentang fungsi yang dimiliki sebuah tipe, bukan oleh satu nilai.
  • Semua error compiler: daftar lengkap halaman error.

Kuis singkat

1. let opts = Default::default(); memberi E0790. Options sudah derive Default. Baris mana yang memperbaikinya?

2. Kenapa let s: Settings = Default::default(); bisa di-compile, tapi let n: String = Shape::name(); tetap memberi E0790?

3. Di dalam fn show<T: Shape>(), bagaimana seharusnya kamu memanggil fungsi trait name()?

Skor: 0 / 3