Sepanjang section ini semua trait dan semua tipe hidup rukun dalam satu crate, sehingga setiap impl Trait for Type yang kita tulis selalu diterima. Ekosistem Rust yang sesungguhnya jauh lebih ramai: program kita menggabungkan crate sendiri dengan crate dari registri publik, dan setiap crate bisa saja menuliskan implementasi trait untuk tipe yang sama. Bayangkan dua crate pihak ketiga masing-masing menulis impl Display for Vec<String> dengan format berbeda — pemrogram aplikasi yang memakai keduanya akan bingung implementasi mana yang berlaku, dan pemanggilan format!("{}", daftar) menjadi ambigu. Rust memutuskan kekacauan itu sejak desain dengan prinsip coherence: untuk setiap pasangan trait dan tipe, boleh ada paling banyak satu implementasi di seluruh program, di mana pun implementasi itu ditulis. Penjaga gerbangnya bernama orphan rule — dan pada chapter ini kita melihat aturan itu menolak kode yang tampak polos, lalu mempelajari pola newtype sebagai jalan keluarnya.
Janji Koherensi Dimulai dari Crate Sendiri #
Bentuk paling sederhana dari janji koherensi berlaku di dalam satu crate: blok impl yang sama tidak boleh ditulis dua kali untuk pasangan trait-tipe yang sama. Pelanggarannya terdeteksi langsung dengan error E0119:
trait Summary {
fn summarize(&self) -> String;
}
struct Tweet {
username: String,
content: String,
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{} menulis: {}", self.username, self.content)
}
}
fn main() {
let tweet = Tweet {
username: String::from("@budi_perjalanan"),
content: String::from("Hujan deras di Bogor."),
};
println!("{}", tweet.summarize());
}Pesan error kompilator:
error[E0119]: conflicting implementations of trait `Summary` for type `Tweet`
--> main.rs:16:1
|
10 | impl Summary for Tweet {
| ---------------------- first implementation here
...
16 | impl Summary for Tweet {
| ^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `Tweet`
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0119`.Pesan E0119 menunjuk kedua pelaku sekaligus: implementasi pertama di baris 10 dan implementasi konflik di baris 16. Penolakan ini terasa jelas bahkan tanpa penjelasan — dua definisi method summarize untuk tipe yang sama membuat method resolution mustahil menentukan panggilan tweet.summarize() mengarah ke mana. Coherence memastikan setiap pemanggilan method punya jawaban tunggal.
Aturan Orphan untuk Tipe dan Trait Asing #
Ke dalam satu crate, aturannya mudah ditegakkan. Persoalannya melintasi batas crate: siapa yang berhak menulis impl Summary for Vec<String> — kita yang memakai Vec, atau std yang memiliki Vec? Orphan rule menjawab dengan satu kalimat: blok impl Trait for Type hanya boleh ditulis pada crate yang mendefinisikan trait tersebut atau tipe tersebut — minimal salah satunya milik kita. Nama “orphan” (yatim) merujuk pada impl yang tidak punya induk di crate tempat impl itu berada: trait asing untuk tipe asing adalah “yatim” dan ditolak.
| Trait | Tipe | Boleh? | Alasan |
|---|---|---|---|
| lokal (kita definisikan) | lokal | Boleh | Sudah kita lakukan sepanjang section ini |
| lokal | asing (mis. Vec<String>) | Boleh | Trait milik kita |
asing (mis. Display) | lokal | Boleh | Tipe milik kita |
| asing | asing | Ditolak | Orphan rule |
Coba kita langgar baris terakhirnya: memberi kemampuan pencetakan {} kepada Vec<String> dari crate std:
use std::fmt;
impl fmt::Display for Vec<String> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let joined = self.join(", ");
write!(f, "[{}]", joined)
}
}
fn main() {
let names = vec![
String::from("Budi Santoso"),
String::from("Siti Rahma"),
];
println!("Peserta: {}", names);
}Pesan error kompilator:
error[E0117]: only traits defined in the current crate can be implemented for types defined outside of the crate
--> main.rs:3:1
|
3 | impl fmt::Display for Vec<String> {
| ^^^^^^^^^^^^^^^^^^^^^^-----------
| |
| `Vec` is not defined in the current crate
|
= note: impl doesn't have any local type before any uncovered type parameters
= note: for more information see https://doc.rust-lang.org/reference/items/implementations.html#orphan-rules
= note: define and implement a trait or new type instead
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0117`.Pesan E0117 menyebut inti aturannya di judul: hanya trait yang didefinisikan di crate sekarang yang boleh diimplementasikan untuk tipe dari luar crate. Catatan bawahnya merujuk langsung ke dokumentasi orphan rules di referensi bahasa, dan saran terakhirnya — “define and implement a trait or new type instead” — adalah petunjuk menuju solusi resmi yang akan kita praktikkan sebentar lagi.
Jalan Keluarnya: Pola newtype #
Ketika kebutuhan menampilkan Vec<String> dengan format {} memang nyata, solusinya bukan melanggar aturan melainkan menjadikan salah satu pihak lokal: bungkus tipe asing dalam struct baru milik kita. Pola ini disebut newtype — tipe baru pembungkus tipe lama:
use std::fmt;
struct Wrapper(Vec<String>);
impl fmt::Display for Wrapper {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "[{}]", self.0.join(", "))
}
}
fn main() {
let participants = Wrapper(vec![
String::from("Budi Santoso"),
String::from("Siti Rahma"),
String::from("Citra Lestari"),
]);
println!("Peserta lokakarya: {}", participants);
}Output yang Dihasilkan:
Peserta lokakarya: [Budi Santoso, Siti Rahma, Citra Lestari]Mari kita bedah mekanisme kesahannya:
- Baris 3 (
struct Wrapper(Vec<String>);): newtype — tuple struct dengan satu field. TraitDisplaytetap asing, tetapi kini tipe penerimaWrapperadalah lokal; salah satu pihak sudah milik kita, sehingga impl tidak lagi “yatim”. - Baris 5 (
impl fmt::Display for Wrapper {): blok impl sah karenaWrapperdidefinisikan di crate ini. Hanya tipeWrapperyang mendapat kemampuan pencetakan —Vec<String>asli di mana pun tetap tidak bisa dicetak dengan{}. - Baris 6 (
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result): methodfmtmilik kontrakDisplay— implementasi manual inilah yang format{}cari. Sintaks tanpa penandaan lifetime padaFormatterditerima karena Rust meng-isinya otomatis di posisi argumen. - Baris 7 (
write!(f, "[{}]", self.0.join(", "))): field pertama tuple struct diakses lewatself.0.join(", ")menggabungkan seluruh elemen menjadi satu teks dipisah koma. - Baris 12-16 (
let participants = Wrapper(vec![...]);): nilaiVec<String>dibungkus saat dibuat — biaya pembungkusan nyaris nol karenaWrapperhanyalah lapisan tipis tanpa field tambahan.
Pola newtype memang menuntut pembungkusan dan pembukaan (self.0, atau wrapper.0 untuk mengambil kembali Vec di dalamnya), tetapi biaya kecerobohan itu kecil dibanding jaminan yang dibayar: implementasi kita tidak akan pernah bertabrakan dengan implementasi crate lain di masa depan, dan pembaca kode melihat dengan jelas bahwa perilaku khusus ini melekat pada tipe pembungkus kita, bukan pada tipe asing yang dipakai semua orang.
Ringkasan #
Coherence menjanjikan satu hal yang membuat ekosistem crate tetap waras: satu pasangan trait-tipe hanya boleh punya satu implementasi di seluruh program. Di dalam satu crate, pelanggarannya ditolak dengan E0119 yang menunjuk kedua blok impl yang bertabrakan. Antar-crate, penjaganya adalah orphan rule melalui E0117: impl Trait for Type hanya boleh ditulis pada crate pemilik trait atau pemilik tipe — trait asing untuk tipe asing adalah impl yatim yang ditolak. Kebutuhan yang nyata tetap terlayani lewat pola newtype: bungkus tipe asing dalam struct lokal, implementasikan trait untuk pembungkusnya, dan seluruh jaminan koherensi tetap utuh. Aturan-aturan main ini menutup dasar-dasar penulisan impl; satu topik praktis tersisa yang memperlihatkan trait bekerja di balik sintaks yang kita pakai setiap hari: operator aritmetika.