Lewati ke konten

Error Rust E0404: expected trait, found struct or derive macro (bukan trait)

E0404 artinya: Rust menemukan nama yang kamu tulis, tapi nama itu bukan trait, padahal hanya trait yang boleh ada di tempat itu. Tempat-tempat itu adalah impl ___ for Type, bound seperti T: ___, dan dyn ___ atau impl ___. Kasus yang paling sering justru mengejutkan banyak orang: impl Debug for Point tanpa import. Debug memang ada di scope, tapi sebagai makro #[derive(Debug)], bukan sebagai trait. Kasus umum lainnya adalah struct yang dipakai seolah-olah trait, yang sering terjadi kalau kamu terbiasa dengan class dan inheritance (pewarisan). Keduanya cepat diperbaiki. (Halaman ini bagian dari Penjelasan error compiler Rust.)

error[E0404]: expected trait, found derive macro `Debug`
--> src/main.rs:6:6
|
6 | impl Debug for Point { // ❌ `Debug` di sini adalah derive macro, bukan trait
| ^^^^^ not a trait
|
help: consider importing this trait instead
|
1 + use std::fmt::Debug;
|

Ini program yang menghasilkan error tersebut:

struct Point {
x: i32,
y: i32,
}
impl Debug for Point { // ❌ `Debug` di sini adalah derive macro, bukan trait
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "Point di ({}, {})", self.x, self.y)
}
}
fn main() {
let p = Point { x: 3, y: 4 };
println!("{:?}", p);
}

^^^^^ menunjuk ke Debug di baris 6, dengan label “not a trait” (bukan trait). Judulnya menyebut apa yang ditemukan Rust sebagai gantinya: sebuah derive macro bernama Debug.

Itu karena ada dua hal yang bernama Debug. Makro Debug, yang kamu pakai sebagai #[derive(Debug)], ada di prelude, jadi setiap file bisa melihatnya. Trait std::fmt::Debug, yang kamu butuhkan untuk impl Debug for ..., tidak ada di prelude. Jadi Rust menemukan satu-satunya Debug yang bisa dilihatnya, menyadari bahwa itu makro, lalu berhenti. Baris help memberi import yang memasukkan trait-nya. Hash bekerja dengan cara yang sama: expected trait, found derive macro `Hash` , diperbaiki dengan use std::hash::Hash;.

Kalau judulnya bilang found struct `Animal` (atau enum, atau type alias), nama itu adalah salah satu tipe buatanmu sendiri, yang dipakai di tempat yang seharusnya diisi trait.

Ini seperti meminta “seorang sopir bersertifikat” lalu malah diberi sebuah mobil. Mobil itu nyata, dan sangat berkaitan, tapi pekerjaannya membutuhkan seseorang yang bisa menyetir: sekumpulan kemampuan, dan itulah trait.

Rust memisahkan data dari perilaku. Struct atau enum menggambarkan data: sebuah nilai itu apa. Trait menggambarkan perilaku: sebuah tipe bisa melakukan apa. impl Trait for Type menghubungkan keduanya: “tipe ini bisa melakukan hal-hal ini”. Bound seperti T: Trait berarti “tipe apa pun yang bisa melakukan hal-hal ini”.

Struct tidak bisa dipakai di tempat-tempat itu, karena struct bukan daftar kemampuan. Dan Rust tidak punya inheritance, jadi impl Animal for Dog tidak bisa berarti “Dog adalah sejenis Animal” kalau Animal adalah struct. Kalau kamu ingin beberapa tipe berbagi perilaku, bagian yang dibagi itu adalah trait. Lihat Trait dan Generics.

Rust juga menyimpan makro dan trait di namespace yang terpisah, itulah sebabnya Debug bisa menjadi makro sekaligus trait. Derive macro-nya ada di prelude supaya #[derive(Debug)] bisa dipakai di mana saja; trait-nya hanya perlu disebut saat kamu sendiri mengimplementasikannya atau memakainya sebagai bound.

Perbaikan 1: Import trait-nya (use std::fmt::Debug;)

Section titled “Perbaikan 1: Import trait-nya (use std::fmt::Debug;)”

Pilih ini kalau pesannya bilang found derive macro dan kamu ingin format sendiri, atau kamu butuh trait itu di bound seperti T: Debug. Tambahkan baris dari help:

use std::fmt::Debug;
struct Point {
x: i32,
y: i32,
}
impl Debug for Point {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "Point di ({}, {})", self.x, self.y)
}
}
fn print_all<T: Debug>(items: &[T]) {
for item in items {
println!("{:?}", item);
}
}
fn main() {
let p = Point { x: 3, y: 4 };
println!("{:?}", p);
print_all(&[Point { x: 0, y: 0 }, Point { x: -1, y: 7 }]);
}

Output:

Point di (3, 4)
Point di (0, 0)
Point di (-1, 7)
▶ Coba sendiri

Alternatif yang sering dipakai adalah use std::fmt;, lalu impl fmt::Debug for Point dengan fmt::Formatter dan fmt::Result. Keduanya sama-sama boleh; pilih satu gaya per file.

Perbaikan 2: Pakai #[derive(Debug)] alih-alih menulis impl-nya

Section titled “Perbaikan 2: Pakai #[derive(Debug)] alih-alih menulis impl-nya”

Pilih ini kalau kamu tidak butuh format kustom, dan untuk Debug itu yang paling sering terjadi. Derive macro menuliskan seluruh impl Debug for Point untukmu, dan kamu tidak butuh import apa pun:

#[derive(Debug)] // ✅ derive macro menuliskan `impl Debug for Point` untukmu
struct Point {
x: i32,
y: i32,
}
fn main() {
let p = Point { x: 3, y: 4 };
println!("{:?}", p);
println!("{:#?}", p);
println!("jarak dari (0, 0) di grid: {}", p.x.abs() + p.y.abs());
}

Output:

Point { x: 3, y: 4 }
Point {
x: 3,
y: 4,
}
jarak dari (0, 0) di grid: 7
▶ Coba sendiri

Pakai salah satunya, jangan keduanya: #[derive(Debug)] ditambah impl Debug buatanmu sendiri memberi error “conflicting implementations” (implementasi yang bentrok, E0119, bahasa Inggris). {:#?} adalah bentuk yang rapi dan multi-baris. Selengkapnya di Trait yang Sering Dipakai.

Perbaikan 3: Definisikan trait untuk perilaku yang dibagi

Section titled “Perbaikan 3: Definisikan trait untuk perilaku yang dibagi”

Pilih ini kalau pesannya bilang found struct di baris impl X for Y. Kamu mungkin ingin Dog menjadi “sejenis Animal”, seperti subclass:

error[E0404]: expected trait, found struct `Animal`
--> src/main.rs:7:6
|
7 | impl Animal for Dog { // ❌ `Animal` itu struct, bukan trait
| ^^^^^^ not a trait

Program di baliknya:

struct Animal {
name: String,
}
struct Dog;
impl Animal for Dog { // ❌ `Animal` itu struct, bukan trait
fn name(&self) -> String {
String::from("Rex")
}
}
fn main() {
println!("{}", Dog.name());
}

Di Rust, bagian yang dibagi adalah sebuah trait: daftar method yang harus disediakan setiap tipe, ditambah method default kalau perlu. Datanya tetap ada di masing-masing struct:

trait Animal { // ✅ sebuah trait: apa yang bisa dilakukan setiap hewan
fn name(&self) -> String;
fn greet(&self) -> String { // method default
format!("Halo, aku {}!", self.name())
}
}
struct Dog;
struct Cat {
name: String, // datanya tinggal di struct
}
impl Animal for Dog {
fn name(&self) -> String {
String::from("Rex")
}
}
impl Animal for Cat {
fn name(&self) -> String {
self.name.clone()
}
}
fn main() {
println!("{}", Dog.greet());
let tom = Cat { name: String::from("Tom") };
println!("{}", tom.greet());
}

Output:

Halo, aku Rex!
Halo, aku Tom!
▶ Coba sendiri

Kalau kamu hanya ingin menambah method ke satu struct, kamu sama sekali tidak butuh trait: tulis impl Dog { fn name(&self) -> String { ... } }, tanpa for. Lihat Mendefinisikan trait.

Perbaikan 4: Terima struct-nya langsung, atau pakai bound pada trait

Section titled “Perbaikan 4: Terima struct-nya langsung, atau pakai bound pada trait”

Pilih ini kalau error-nya menunjuk ke bound seperti T: Shape atau impl Shape, dan Shape adalah struct buatanmu:

error[E0404]: expected trait, found struct `Shape`
--> src/main.rs:5:16
|
5 | fn describe<T: Shape>(item: &T) { // ❌ `Shape` itu struct, bukan trait
| ^^^^^ not a trait

Program di baliknya:

struct Shape {
sides: u32,
}
fn describe<T: Shape>(item: &T) { // ❌ `Shape` itu struct, bukan trait
println!("a shape");
}
fn main() {
describe(&Shape { sides: 3 });
}

Bound berarti “tipe apa pun yang mengimplementasikan trait ini”. Kalau fungsinya hanya pernah menerima Shape, buang generic-nya dan terima &Shape. Kalau fungsinya harus bisa dipakai untuk banyak tipe, taruh perilaku yang dibagi di sebuah trait dan pakai trait itu sebagai bound:

struct Shape {
sides: u32,
}
fn describe(item: &Shape) { // ✅ terima struct-nya langsung, tidak perlu generic
println!("bentuk dengan {} sisi", item.sides);
}
trait Named { // ✅ atau sebuah trait, untuk "tipe apa pun yang punya nama"
fn name(&self) -> String;
}
impl Named for Shape {
fn name(&self) -> String {
match self.sides {
3 => String::from("segitiga"),
4 => String::from("persegi"),
n => format!("segi-{}", n),
}
}
}
fn shout<T: Named>(item: &T) { // ✅ trait bound butuh sebuah trait
println!("{}!", item.name().to_uppercase());
}
fn main() {
let t = Shape { sides: 3 };
describe(&t);
shout(&t);
shout(&Shape { sides: 6 });
}

Output:

bentuk dengan 3 sisi
SEGITIGA!
SEGI-6!
▶ Coba sendiri

Trait bound dijelaskan di Generics dan Trait.

impl Vec<i32> sebagai tipe parameter. fn total(x: impl Vec<i32>) memberi expected trait, found struct `Vec` . impl di posisi itu butuh sebuah trait. Perbaikannya: terima &[i32] atau Vec<i32>, atau impl IntoIterator<Item = i32> kalau kamu ingin menerima koleksi apa saja.

Box<dyn String>. dyn juga butuh trait, jadi ini memberi expected trait, found struct `String` , dan rustc menyarankan ToString. Perbaikannya: Box<dyn ToString> kalau yang kamu maksud “apa pun yang bisa menjadi string”, atau cukup String. Lihat Trait Object.

Type alias untuk sebuah trait. type Printable = dyn std::fmt::Display; lalu impl Printable for A memberi expected trait, found type alias `Printable` dengan label “type aliases cannot be used as traits” (type alias tidak bisa dipakai sebagai trait). Perbaikannya: tulis nama asli trait-nya di impl.

Kuis singkat

1. impl Debug for Point memberi "expected trait, found derive macro Debug". Apa perbaikan paling sederhana kalau kamu ingin format kustom?

2. Kamu punya struct Animal dan menulis impl Animal for Dog. Apa yang diinginkan Rust sebagai gantinya?

3. Mana yang boleh ditulis setelah T: di fn show<T: ___>(x: T)?

Skor: 0 / 3