Kontrak trait pada dua chapter sebelumnya bersifat keras: setiap tipe yang menandatanganinya harus menuliskan seluruh method sendiri. Ketegasan itu sehat untuk kontrak kecil, tetapi bayangkan trait dengan lima method di mana hampir semua tipe hanya peduli pada satu — sisanya ditulis berulang dengan isi yang nyaris seragam di setiap blok impl. Kebutuhan nyatanya memang begitu: pada aplikasi berita, ringkasan cuitan, foto, dan video sering kali cukup memakai pola yang sama, sementara hanya artikel yang punya format khusus. Rust mengizinkan trait menyediakan default implementation — body method yang ditulis langsung di dalam definisi trait dan otomatis berlaku untuk setiap tipe yang tidak menuliskan method tersebut sendiri. Babak menimpa perilaku bawaan itu pembahasan chapter berikutnya; sekarang kita fokus memakai bawaan apa adanya.
Menulis Body Bawaan di Trait #
Perbedaan sintaksnya hanya satu tanda: method yang punya default ditulis lengkap dengan body di dalam trait, bukan diakhiri titik koma. Kita rombak kontrak Summary agar summarize memiliki bawaan yang dibangun di atas method wajib summarize_author:
trait Summary {
fn summarize_author(&self) -> String;
fn summarize(&self) -> String {
format!("(Baca selengkapnya dari {}...)", self.summarize_author())
}
}
struct NewsArticle {
author: String,
}
struct Tweet {
username: String,
}
impl Summary for NewsArticle {
fn summarize_author(&self) -> String {
self.author.clone()
}
}
impl Summary for Tweet {
fn summarize_author(&self) -> String {
format!("@{}", self.username)
}
}
fn main() {
let article = NewsArticle {
author: String::from("Rina Wijaya"),
};
let tweet = Tweet {
username: String::from("budi_perjalanan"),
};
println!("Ringkasan artikel: {}", article.summarize());
println!("Ringkasan cuitan: {}", tweet.summarize());
}Output yang Dihasilkan:
Ringkasan artikel: (Baca selengkapnya dari Rina Wijaya...)
Ringkasan cuitan: (Baca selengkapnya dari @budi_perjalanan...)Mari kita bedah mekanisme di baliknya:
- Baris 2 (
fn summarize_author(&self) -> String;): method tanpa body tetap menjadi kewajiban penuh. Kontrak ini hanya menuntut satu hal dari setiap tipe: menyebutkan siapa pembuat kontennya. - Baris 4-6 (
fn summarize(&self) -> String { ... }): method kedua kini memiliki body di dalam trait — inilah default implementation. Tidak ada lagi kewajiban menuliskannya di blok impl; tipe yang diam akan menerima body ini. - Baris 5 (
format!("(Baca selengkapnya dari {}...)", self.summarize_author())): body bawaan memanggil method lain milik trait yang sama. Pemanggilan ini sah justru karenasummarize_authorbersifat wajib: kontrak menjamin method tersebut pasti tersedia pada tipe mana pun, sehingga default dapat menyusun perilaku yang lebih besar dari komponen yang dijamin kontrak. - Baris 17-21 (
impl Summary for NewsArticle { ... }): blok impl hanya berisisummarize_author. Methodsummarizetidak disebut sama sekali, dan justru karena tidak disebut, body bawaan-lah yang dipakai. - Baris 38 (
println!("Ringkasan artikel: {}", article.summarize());): pemanggilansummarize()padaarticletetap sah meskipun blok impl tidak menuliskannya. Kompilator mengikat pemanggilan ke body bawaan dari trait.
Perhatikan outputnya: kedua tipe menerima bentuk ringkasan yang sama persis — “(Baca selengkapnya dari …)” — karena keduanya memakai bawaan yang sama. Bagian yang berbeda hanya berasal dari summarize_author yang masing-masing tipe isi sendiri: Rina Wijaya untuk artikel dan @budi_perjalanan untuk cuitan.
Efek samping positifnya tersirar pada error E0046 dari chapter sebelumnya. Kontrak lama menuntut summarize dan preview ditulis di setiap blok impl; kontrak baru hanya menuntut summarize_author. Method yang punya default berhenti menjadi kewajiban penulisan, sehingga blok impl yang tadinya terancam E0046 kini lengkap hanya dengan satu method. Default dengan demikian menggeser beban: penulis trait menyediakan perilaku standar, penulis tipe cukup menyediakan bagian yang benar-benar khas per tipe.
Kontrak Sepenuhnya Bawaan dan impl Kosong #
Bentuk paling longgar terjadi ketika semua method pada trait memiliki default. Kontrak seperti itu tidak menuntut apa pun secara eksplisit, sehingga blok impl boleh ditulis kosong — kurung kurawal tanpa isi — sebagai pernyataan resmi bahwa tipe tersebut masuk ke dalam kontrak dan menerima seluruh bawaan:
trait Greeting {
fn greet(&self) -> String {
String::from("Selamat datang di layanan notifikasi!")
}
}
struct Guest;
impl Greeting for Guest {}
fn main() {
let guest = Guest;
println!("{}", guest.greet());
}Output yang Dihasilkan:
Selamat datang di layanan notifikasi!Tiga baris layak diperhatikan: Baris 9 (impl Greeting for Guest {}) adalah impl kosong — sah karena trait Greeting tidak memiliki method wajib; Baris 12 (let guest = Guest;) membuat nilai Guest, tipe tanpa field yang kita kenal sebagai unit struct dari section Struktur Data; dan Baris 13 (println!("{}", guest.greet());) memanggil method bawaan yang tidak pernah kita tulis di blok impl mana pun. Pola “trait sepenuhnya bawaan” ini populer sebagai penanda kategori — tipe yang mengimplementasikannya menyatakan keanggotaan, bukan menambahkan perilaku baru.
Kapan Default Membantu #
Default implementation bekerja paling baik pada pola berikut:
- Kebanyakan tipe berperilaku sama, sedikit yang khusus. Wajib menulis
summarize_author, bawaan menyusunsummarize— tipe umum menulis satu method, tipe istimewa bebas menulis keduanya. - Trait disusun seperti lapisan. Method wajib menjadi komponen kecil yang mengambil bagian khas tipe, sedangkan method bawaan menyusun komponen-komponen itu menjadi perilaku utuh. Kontrak
Summarykita memakai pola ini:summarize_authormengambil nama,summarizemerangkainya. - Standar yang bisa ditinggalkan. Default memberi titik awal yang langsung jalan, sehingga tipe baru tidak pernah terblokir oleh trait yang besar; bawaan selalu menunggu di belakang.
- Kontrak tetap tumbuh dengan aman. Trait yang sudah dipakai banyak tipe boleh menambah method baru tanpa mematahkan implementasi lama, asalkan method baru itu diberi default. Penulis pustaka memakai jalan ini terus-menerus saat memperkaya API tanpa memaksa seluruh pengguna menulis ulang kode.
Kerugian default juga perlu disebut dengan jujur. Bawaan hanya bisa mengandalkan method kontrak — bukan field — sehingga perilaku bawaan yang cerdas terbatas pada informasi yang dijanjikan signature. Bawaan juga menyembunyikan kode: pembaca yang melihat pemanggilan article.summarize() harus membuka definisi trait untuk tahu perilaku yang sebenarnya berjalan. Untuk trait kecil seperti Summary beban baca itu ringan; untuk trait besar, tiap default layak diberi komentar atau dokumentasi yang menjelaskan asumsinya.
Ringkasan #
Trait boleh melengkapi signature dengan body bawaan: method dengan default tidak wajib ditulis ulang di blok impl, dan tipe yang membiarkannya menerima body dari trait secara otomatis. Body bawaan bahkan dapat memanggil method wajib lain karena kontrak menjamin keberadaannya — pola yang mengubah trait menjadi lapisan komponen kecil plus perilaku rakitan. Pada ujung paling longgar, trait yang sepenuhnya bawaan cukup diikrarkan lewat impl kosong. Pertanyaan berikutnya sudah menganga: apa yang terjadi justru ketika sebuah tipe tidak puas dengan bawaan itu? Jawabannya adalah override.