Lewati ke konten

Error Rust E0277: the trait bound is not satisfied (trait tidak terpenuhi)

E0277 artinya: kamu memakai sebuah tipe di tempat yang membutuhkan kemampuan yang tidak dimiliki tipe itu. Mencetaknya, membandingkannya, menjumlahkannya, mengonversinya, mengirimnya ke thread lain: masing-masing butuh sebuah trait, dan tipemu belum punya trait itu. Ini salah satu error Rust yang paling sering muncul. Pesannya biasanya sudah menyebutkan perbaikan yang tepat, dan sering kali perbaikannya cuma satu baris. (Halaman ini bagian dari Penjelasan error compiler Rust.)

Versi yang paling sering ditemui pertama kali adalah saat mencetak struct buatan sendiri dengan {}:

error[E0277]: `Player` doesn't implement `std::fmt::Display`
--> src/main.rs:8:20
|
8 | println!("{}", p);
| -- ^ `Player` cannot be formatted with the default formatter
| |
| required by this formatting parameter
|
help: the trait `std::fmt::Display` is not implemented for `Player`
= note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead

Compiler bilang Player tidak mengimplementasikan Display, jadi tidak bisa dicetak dengan {}, dan menyarankan {:?} sebagai gantinya.

Program yang menyebabkannya:

struct Player {
name: String,
score: u32,
}
fn main() {
let p = Player { name: String::from("Ana"), score: 42 };
println!("{}", p);
}

Trait adalah janji bahwa sebuah tipe bisa melakukan sesuatu. Display adalah janji “aku tahu cara mencetak diriku dengan rapi”. PartialEq artinya “aku bisa dibandingkan dengan ==”. Send artinya “aku aman dipindahkan ke thread lain”.

Kode seperti println!, .sort(), +, dan thread::spawn meminta salah satu janji itu. Compiler menunjuk nilai yang kamu berikan (p di baris 8) dan bilang: tipe ini tidak pernah membuat janji itu, jadi aku tidak bisa memakainya di sini.

Bayangkan stopkontak. Stopkontak hanya menerima steker dengan satu bentuk tertentu. Alatmu mungkin berfungsi sempurna, tapi kalau stekernya tidak pas, ya tidak bisa dicolokkan. E0277 adalah cara compiler bilang “stekernya salah”. Kamu memperbaikinya dengan memberi tipe itu steker yang benar: biasanya lewat #[derive(...)], kadang dengan blok impl pendek.

Rust tidak pernah menebak. Untuk tipe Player buatanmu, tidak ada satu cara “yang benar” untuk mencetak, membandingkan, atau mengurutkannya. Apakah dua pemain sama kalau namanya sama, atau hanya kalau semua field-nya sama? Rust memintamu menyatakannya, alih-alih diam-diam melakukan sesuatu yang tidak kamu maksud.

Pemeriksaan ini terjadi saat kompilasi. Di banyak bahasa, mencetak objek yang belum diajari cara mencetak dirinya menghasilkan sesuatu yang tidak berguna seperti <Player object at 0x7f...>, dan mengirim nilai yang tidak aman untuk thread bisa berujung crash langka di production. Di Rust, keduanya adalah error kompilasi yang cukup kamu perbaiki sekali.

Cara kerja trait dijelaskan di halaman Trait, dan trait sehari-hari (Debug, Display, PartialEq, Ord, Hash) di Trait yang sering dipakai.

E0277 punya banyak wajah. Cari pesan yang cocok dengan error-mu di bawah ini.

Perbaikan 1: Mencetak struct dengan {} atau {:?}

Section titled “Perbaikan 1: Mencetak struct dengan {} atau {:?}”

{:?} butuh Debug. Kamu hampir tidak pernah menulisnya sendiri: tambahkan #[derive(Debug)]. Pakai ini untuk debugging dan log. Versi error untuk {:?} terlihat seperti ini:

error[E0277]: `Player` doesn't implement `Debug`
= help: the trait `Debug` is not implemented for `Player`
= note: add `#[derive(Debug)]` to `Player` or manually `impl Debug for Player`

Catatannya bilang: tambahkan #[derive(Debug)] ke Player, atau tulis impl Debug for Player sendiri.

{} butuh Display. Rust tidak bisa men-derive-nya, karena hanya kamu yang tahu seperti apa versi “rapi”-nya. Tulis impl kecil dengan write!. Pakai ini untuk teks yang akan dilihat pengguna.

use std::fmt;
#[derive(Debug)] // {:?} langsung tersedia
struct Player {
name: String,
score: u32,
}
// Display kamu tulis sendiri: ini menentukan apa yang dicetak {}.
impl fmt::Display for Player {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{} ({} poin)", self.name, self.score)
}
}
fn main() {
let p = Player { name: String::from("Ana"), score: 42 };
println!("{:?}", p);
println!("{}", p);
}

Output:

Player { name: "Ana", score: 42 }
Ana (42 poin)
▶ Coba sendiri

Perbaikan 2: Membandingkan, mengurutkan, atau memakai sebagai key HashMap

Section titled “Perbaikan 2: Membandingkan, mengurutkan, atau memakai sebagai key HashMap”

.contains() dan == butuh PartialEq. .sort() butuh Ord. Key HashSet dan HashMap butuh Eq dan Hash. Pesannya terlihat seperti ini:

error[E0277]: can't compare `Point` with `Point`
= help: the trait `PartialEq` is not implemented for `Point`
error[E0277]: the trait bound `Priority: Ord` is not satisfied
= help: the trait `Ord` is not implemented for `Priority`
error[E0277]: the trait bound `Point: Hash` is not satisfied
= help: the trait `Hash` is not implemented for `Point`

Ketiganya bilang hal yang sama: trait tertentu (PartialEq, Ord, Hash) belum diimplementasikan untuk tipemu. Perbaikannya adalah men-derive trait-trait itu. Mereka saling bergantung, jadi set lengkap yang biasa dipakai adalah PartialEq, Eq, Hash, PartialOrd, Ord:

use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
enum Priority {
Low,
Medium,
High,
}
fn main() {
let mut tasks = vec![Priority::High, Priority::Low, Priority::Medium];
tasks.sort(); // butuh Ord
println!("{:?}", tasks);
println!("Ada yang High: {}", tasks.contains(&Priority::High)); // butuh PartialEq
let mut counts: HashMap<Priority, u32> = HashMap::new(); // key butuh Eq + Hash
for task in &tasks {
*counts.entry(*task).or_insert(0) += 1;
}
println!("Tugas Low: {}", counts[&Priority::Low]);
}

Output:

[Low, Medium, High]
Ada yang High: true
Tugas Low: 1
▶ Coba sendiri

Ord hasil derive mengurutkan varian enum sesuai urutan kamu menuliskannya, dan struct field demi field dari atas ke bawah. Satu catatan jujur: tipe dengan field f64 tidak bisa men-derive Eq, Hash, atau Ord, karena float punya NaN, yang tidak sama dengan dirinya sendiri. Untuk mengurutkan float, lihat Mengurutkan dan mencari.

Perbaikan 3: Mencampur tipe angka (cannot add f64 to i32)

Section titled “Perbaikan 3: Mencampur tipe angka (cannot add f64 to i32)”
error[E0277]: cannot add `f64` to `i32`
--> src/main.rs:4:23
|
4 | let total = items + price;
| ^ no implementation for `i32 + f64`
|
= help: the trait `Add<f64>` is not implemented for `i32`

Artinya: tidak ada cara menjumlahkan i32 dengan f64. Rust tidak pernah mengonversi angka untukmu, bahkan i32 ke f64 pun tidak. Kedua sisi +, -, *, dan / harus bertipe sama. Konversi salah satu sisi sendiri dengan as:

fn main() {
let items: i32 = 3;
let price: f64 = 2.5;
let total = items as f64 * price; // konversi dulu, baru dikalikan
println!("Total: {total}");
}

Output:

Total: 7.5
▶ Coba sendiri

f64::from(items) juga bisa, dan tidak mungkin kehilangan data. Arah sebaliknya (f64 ke i32) membuang angka desimalnya, jadi pikirkan pembulatan dulu. Halaman Konversi tipe dan Konversi membahas detailnya.

Perbaikan 4: ? couldn’t convert the error

Section titled “Perbaikan 4: ? couldn’t convert the error”
error[E0277]: `?` couldn't convert the error to `ConfigError`
|
8 | fn parse_port(text: &str) -> Result<u16, ConfigError> {
| ------------------------ expected `ConfigError` because of this
9 | let port = text.parse::<u16>()?;
| --------------^ the trait `From<ParseIntError>` is not implemented for `ConfigError`
|
= note: the question mark operation (`?`) implicitly performs a conversion on the error value using the `From` trait

Pesan ini berarti ? tidak bisa mengubah ParseIntError menjadi ConfigError, karena tidak ada From<ParseIntError> untuk ConfigError. Saat ? bertemu error, ia mengembalikan error itu dari fungsimu. Kalau tipe error-nya berbeda dengan yang dijanjikan fungsimu, ? mencoba mengonversinya dengan From. Tugasmu adalah menulis From itu:

use std::num::ParseIntError;
#[derive(Debug)]
enum ConfigError {
BadPort(ParseIntError),
}
// Ajari `?` cara mengubah ParseIntError menjadi ConfigError.
impl From<ParseIntError> for ConfigError {
fn from(err: ParseIntError) -> Self {
ConfigError::BadPort(err)
}
}
fn parse_port(text: &str) -> Result<u16, ConfigError> {
let port = text.parse::<u16>()?;
Ok(port)
}
fn main() {
println!("{:?}", parse_port("8080"));
println!("{:?}", parse_port("delapan puluh"));
}

Output:

Ok(8080)
Err(BadPort(ParseIntError { kind: InvalidDigit }))
▶ Coba sendiri

Pilihan bagus lainnya: konversi satu pemanggilan saja dengan .map_err(ConfigError::BadPort)?, pakai #[from] dari crate thiserror, atau kembalikan Result<T, Box<dyn std::error::Error>> di program kecil. Lihat Error kustom dan anyhow dan thiserror.

Perbaikan 5: Rc cannot be sent between threads safely

Section titled “Perbaikan 5: Rc cannot be sent between threads safely”
error[E0277]: `Rc<String>` cannot be sent between threads safely
--> src/main.rs:6:32
|
6 | let handle = thread::spawn(move || {
| ------------- required by a bound introduced by this call
...
= help: within `{closure@src/main.rs:6:32: 6:39}`, the trait `Send` is not implemented for `Rc<String>`

Artinya Rc<String> tidak aman dikirim ke thread lain, karena ia tidak Send. Rc menghitung owner-nya dengan angka biasa, yang bisa diubah dua thread sekaligus sampai rusak. Karena itu Rc bukan Send. Pakai Arc (versi “atomic”) sebagai gantinya. Cara kerjanya sama, dan aman dipakai lintas thread:

use std::sync::Arc;
use std::thread;
fn main() {
let config = Arc::new(String::from("debug"));
let for_thread = Arc::clone(&config); // owner kedua untuk thread-nya
let handle = thread::spawn(move || {
println!("Thread melihat: {for_thread}");
});
handle.join().unwrap();
println!("Main masih punya: {config}");
}

Output:

Thread melihat: debug
Main masih punya: debug
▶ Coba sendiri

Hal yang sama berlaku untuk RefCell: lintas thread, pakai Arc<Mutex<T>>, bukan Rc<RefCell<T>>. Lihat Shared state dan Konkurensi.

  • Fungsi generik. fn announce<T: Display>(item: T) yang dipanggil dengan struct-mu menghasilkan `Song` doesn't implement `std::fmt::Display` dengan keterangan “required by a bound in announce” (dibutuhkan oleh batasan di announce). Perbaikannya: implementasikan trait yang diminta batasan itu.
  • Test. assert_eq!(a, b) butuh PartialEq dan Debug (untuk mencetak nilainya kalau test gagal). Perbaikannya: #[derive(Debug, PartialEq)].
  • .collect() ke tipe yang salah. let nums: Vec<i32> = words.iter().collect(); menghasilkan “a value of type Vec<i32> cannot be built from an iterator over elements of type &String” (Vec<i32> tidak bisa dibangun dari iterator berisi &String). Perbaikannya: konversi setiap item dulu, misalnya .map(|w| w.parse().unwrap()). Lihat Iterator.
  • E0599: no method found (method tidak ditemukan). Memanggil .insert() pada HashMap yang key-nya tidak Hash kadang muncul sebagai E0599 (“trait bounds were not satisfied”). Perbaikannya sama: derive trait-nya.
  • E0308: mismatched types (tipe tidak cocok). Kamu memberikan satu tipe di tempat yang mengharapkan tipe lain, misalnya i32 di tempat f64.
  • E0382: borrow of moved value (nilai sudah dipindah). Sering muncul tepat setelah kamu menambahkan Clone atau Copy sambil memikirkan trait.
  • E0369: binary operation == cannot be applied. Versi operator == dari PartialEq yang hilang. Perbaikannya sama.
  • Semua error compiler: daftar lengkap halaman error.

Kuis singkat

1. Kamu mendapat "Order doesn't implement Debug" saat memakai {:?}. Apa perbaikan tercepatnya?

2. Kenapa let total = 3_i32 + 2.5_f64; gagal?

3. Kode thread-mu gagal dengan "Rc<String> cannot be sent between threads safely". Apa yang sebaiknya kamu pakai?

Skor: 0 / 3