Sejauh ini kita sudah mengelompokkan data lewat struct dan enum, lalu menempelkan perilaku pada satu tipe lewat blok impl berisi method. Pendekatan itu memadai selama setiap tipe berjalan sendiri-sendiri, tetapi muncul pertanyaan desain begitu beberapa tipe ternyata berbagi kebutuhan perilaku yang sama. Aplikasi agregator berita, misalnya, memuat artikel panjang dan cuitan singkat yang sama-sama harus bisa diringkas, diperiksa, dan dikirim sebagai notifikasi. Trait adalah jawaban Rust atas kebutuhan tersebut: sebuah deklarasi kontrak perilaku berupa daftar signature method yang harus dipenuhi oleh tipe apa pun yang ingin mengaku memiliki kemampuan itu. Pada chapter ini kita fokus pada cara mendeklarasikan kontraknya — tanda tangan berupa implementasi nyata menyusul pada chapter berikutnya.
Dua Tipe dengan Perilaku yang Sama #
Mari kita mulai dari masalah nyatanya. Aplikasi berita kita memuat dua tipe: NewsArticle untuk artikel jurnalistik dan Tweet untuk cuitan singkat. Keduanya sama-sama butuh kemampuan diringkas, dan tanpa mekanisme bersama, jalan satu-satunya adalah menulis fungsi terpisah untuk masing-masing tipe:
struct NewsArticle {
author: String,
headline: String,
content: String,
}
struct Tweet {
username: String,
content: String,
}
fn summarize_article(article: &NewsArticle) -> String {
format!("{} menulis tentang \"{}\": {}", article.author, article.headline, article.content)
}
fn summarize_tweet(tweet: &Tweet) -> String {
format!("{}: {}", tweet.username, tweet.content)
}
fn main() {
let article = NewsArticle {
author: String::from("Rina Wijaya"),
headline: String::from("Pasar Saham Jakarta Ditutup Menguat"),
content: String::from("Indeks naik 1,2 persen sepanjang sesi perdagangan."),
};
let tweet = Tweet {
username: String::from("budi_perjalanan"),
content: String::from("Sampai di puncak Gunung Bromo! Matahari terbit luar biasa indah."),
};
println!("{}", summarize_article(&article));
println!("{}", summarize_tweet(&tweet));
}Output yang Dihasilkan:
Rina Wijaya menulis tentang "Pasar Saham Jakarta Ditutup Menguat": Indeks naik 1,2 persen sepanjang sesi perdagangan.
budi_perjalanan: Sampai di puncak Gunung Bromo! Matahari terbit luar biasa indah.Program di atas berjalan mulus, tetapi mari kita bedah apa yang sebenarnya terjadi di dalamnya:
- Baris 12 (
fn summarize_article(article: &NewsArticle) -> String): fungsi peringkas pertama menerima pinjaman&NewsArticle. Tipe parameter mengunci fungsi ini pada artikel — cuitan tidak akan pernah bisa masuk. - Baris 13 (
format!(...)): fungsi membangunStringringkasan dari tiga field milikNewsArticle. Seluruh logika peringkasan khusus untuk bentuk data artikel. - Baris 16 (
fn summarize_tweet(tweet: &Tweet) -> String): fungsi kedua melakukan pekerjaan yang secara konsep sama — meringkas — tetapi dideklarasikan terpisah karena tipenya berbeda. Kompilator tidak melihat relasi apa pun antara kedua fungsi ini. - Baris 32 (
println!("{}", summarize_article(&article));): pemanggil harus tahu fungsi mana yang cocok untuk tipe mana. Namasummarize_articledansummarize_tweetadalah dua pengetahuan terpisah yang harus dihafal. - Baris 33 (
println!("{}", summarize_tweet(&tweet));): setiap perilaku baru yang kita inginkan — pratinjau, notifikasi, pengurutan berdasarkan ringkasan — akan menggandakan penulisan fungsi per tipe, tanpa jaminan konsistensi antara salinan-salinan tersebut.
Inilah titik lemah pendekatan fungsi-per-tipe: dua kemampuan yang secara konsep identik tidak punya nama bersama. Fungsi generik dari section Generics pun tidak dapat menolong, karena parameter T yang polos tidak menjanjikan method apa pun untuk dipanggil. Kita butuh cara menyatakan “tipe T harus bisa diringkas” — dan pernyataan semacam itu membutuhkan trait.
Trait sebagai Kontrak Perilaku #
Trait dideklarasikan dengan kata kunci trait, diikuti nama trait dan daftar signature method di dalam kurung kurawal. Trait untuk kemampuan peringkas berita kita tampak seperti ini:
trait Summary {
fn summarize(&self) -> String;
fn preview(&self) -> String;
}Mari kita bedah anatomi deklarasinya:
- Baris 1 (
trait Summary {): kata kuncitraitmemperkenalkan kontrak baru bernamaSummary. Nama trait lazim berupa kata benda kemampuan atau sifat —Summary,Display,Clone— karena trait menjawab pertanyaan “apa yang bisa dilakukan tipe ini”. - Baris 2 (
fn summarize(&self) -> String;): signature method pertama. Tanda titik koma menggantikan body: trait hanya menyatakan apa yang harus tersedia, bukan bagaimana cara kerjanya. Parameter&selfberarti method akan dipanggil pada instansi tipe pelaksana dengan meminjam dirinya sendiri, seperti method biasa pada blokimplyang sudah kita kenal di section Struktur Data. - Baris 3 (
fn preview(&self) -> String;): satu trait boleh memuat banyak signature. Semua method dalam daftar menjadi bagian kontrak — tipe pelaksana nanti harus menyediakan seluruhnya.
Dua aturan penting melekat pada bentuk deklarasi ini. Pertama, trait tidak boleh memuat field data — trait murni menjelaskan perilaku, bukan tempat penyimpanan; data tetap hidup di struct masing-masing tipe. Kedua, signature di dalam trait belum menghasilkan method yang bisa dipanggil siapa pun; deklarasi trait hanya menerbitkan kontrak, seperti lembar perjanjian yang menunggu para pihak menandatanganinya.
Relasi antara kontrak dan tipe yang berpotensi memenuhinya dapat digambarkan sebagai berikut:
Diagram di atas menegaskan posisi trait di tengah: NewsArticle dan Tweet boleh sama-sama menandatangani kontrak Summary, dan begitu ditandatangani, keduanya terikat menyediakan method summarize serta preview. Panah putus-putus sengaja dipakai karena pada chapter ini kontrak baru diterbitkan — belum ada satu tipe pun yang benar-benar menandatanganinya.
Kontrak yang Belum Ditepati #
Karena trait hanya menerbitkan kontrak, kode yang mencoba memanggil method-nya sebelum ada tipe yang mengimplementasikannya akan ditolak kompilator. Program berikut mendefinisikan NewsArticle tanpa mengimplementasikan Summary, lalu tetap nekat memanggil summarize:
trait Summary {
fn summarize(&self) -> String;
fn preview(&self) -> String;
}
struct NewsArticle {
author: String,
headline: String,
content: String,
}
fn main() {
let article = NewsArticle {
author: String::from("Rina Wijaya"),
headline: String::from("Pasar Saham Jakarta Ditutup Menguat"),
content: String::from("Indeks Harga Saham Gabungan ditutup naik."),
};
println!("{}", article.summarize());
}Pesan error kompilator:
error[E0599]: no method named `summarize` found for struct `NewsArticle` in the current scope
--> main.rs:19:28
|
6 | struct NewsArticle {
| ------------------ method `summarize` not found for this struct
...
19 | println!("{}", article.summarize());
| ^^^^^^^^^ method not found in `NewsArticle`
|
= help: items from traits can only be used if the trait is implemented and in scope
note: `Summary` defines an item `summarize`, perhaps you need to implement it
--> main.rs:1:1
|
1 | trait Summary {
| ^^^^^^^^^^^^^
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0599`.Pesan E0599 ini justru menunjukkan bahwa kompilator memahami situasinya dengan tepat. Baris bantuan menyatakan bahwa method dari trait hanya dapat dipakai jika trait tersebut sudah diimplementasikan dan berada dalam scope. Catatan di bawahnya bahkan menyebut pelakunya secara langsung: trait Summary mendefinisikan summarize, “mungkin Anda perlu mengimplementasikannya”. Kompilator tahu method itu ada di suatu tempat — definisinya tertulis jelas di baris pertama program — tetapi belum ada satu pun blok impl yang mengikat summarize pada NewsArticle.
Ringkasan #
Trait menyatakan kontrak perilaku bersama: kata kunci trait diikuti daftar signature method yang diakhiri titik koma, tanpa field data dan tanpa body. Kontrak semacam ini memberi nama pada kemampuan yang sebelumnya terpencar di fungsi-fungsi duplikat seperti summarize_article dan summarize_tweet. Meskipun begitu, deklarasi saja belum cukup — pemanggilan article.summarize() tanpa implementasi ditolak dengan E0599 beserta petunjuk langsung ke trait yang belum ditepati. Pertanyaan berikutnya sudah tersedia di ujung jari: bagaimana cara sebuah tipe menandatangani kontrak itu? Jawabannya adalah blok impl Trait for Type.