Sepanjang section ini fungsi kita masih bekerja pada tipe konkret: notify versi sebelumnya — andai ditulis — hanya akan menerima NewsArticle atau hanya Tweet. Padahal kebutuhan aslinya jauh lebih longgar: fungsi notifikasi tidak peduli bentuk datanya, yang penting nilai yang masuk bisa diringkas. Section Generics sudah memperkenalkan parameter tipe T sebagai jawaban atas duplikasi seperti itu, tetapi T yang polos terlalu toleran — kita belum punya cara memastikan bahwa T mengimplementasikan Summary sehingga item.summarize() sah dipanggil. Trait bound menutup celah ini: deklarasi <T: Summary> membatasi T agar hanya menerima tipe yang memenuhi kontrak. Fungsi menjadi generik sekaligus terjamin — bisa dipanggil untuk banyak tipe, namun hanya untuk tipe yang benar-benar punya perilaku yang dibutuhkan.
Membatasi Tipe Generik dengan Trait Bound #
Bentuk penuhnya menempatkan bound pada daftar parameter generik, tepat setelah nama tipe dengan pemisah titik dua:
trait Summary {
fn summarize(&self) -> String;
}
struct NewsArticle {
author: String,
headline: String,
}
struct Tweet {
username: String,
content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{} menulis \"{}\"", self.author, self.headline)
}
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
fn notify<T: Summary>(item: &T) {
println!("Kabar terbaru! {}", item.summarize());
}
fn main() {
let article = NewsArticle {
author: String::from("Rina Wijaya"),
headline: String::from("Pasar Saham Jakarta Ditutup Menguat"),
};
let tweet = Tweet {
username: String::from("budi_perjalanan"),
content: String::from("Sampai di puncak Gunung Bromo!"),
};
notify(&article);
notify(&tweet);
}Output yang Dihasilkan:
Kabar terbaru! Rina Wijaya menulis "Pasar Saham Jakarta Ditutup Menguat"
Kabar terbaru! budi_perjalanan: Sampai di puncak Gunung Bromo!Mari kita bedah bagian-bagian kuncinya:
- Baris 27 (
fn notify<T: Summary>(item: &T)): deklarasi fungsi generik dengan bound. Bagian<T: Summary>dibaca “untuk setiap tipeTyang mengimplementasikanSummary”. Titik dua berperan seperti tanda “adalah-seorang”:Tharus menjadi anggota kontrakSummary. - Baris 27 (
item: &T): parameter meminjam nilai dengan&T, bukan memindahkannya. Pola&Tpada fungsi generik sama pengertiannya dengan&Stringpada fungsi biasa dari section Ownership — pemanggil tetap memegang kepemilikan. - Baris 28 (
println!("Kabar terbaru! {}", item.summarize());): pemanggilanitem.summarize()sah semata-mata karena bound. Inilah fungsi utama bound: membuka izin memanggil method trait di dalam body fungsi generik. - Baris 42 (
notify(&article);): kompilator menyimpulkanT = NewsArticledari argumen, memeriksa bahwaNewsArticle: Summarybenar (impl ada di baris 15), lalu meloloskan pemanggilan. Pemeriksaan ini berlaku untuk setiap lokasi pemanggilan satu per satu, sehingga menambah tipe baru tidak pernah melonggarkan pemeriksaan yang sudah ada. - Baris 43 (
notify(&tweet);): satu definisinotifyyang sama kini dipakai denganT = Tweet. Tanpa trait, capaian seperti ini hanya bisa dilakukan dengan menulis dua fungsi terpisah — tepat duplikasi yang kita tinggalkan.
Monomorphisasi dan Dispatch Statis #
Keajaiban mekaniknya terjadi saat kompilasi lewat proses yang sudah diperkenalkan section Generics: monomorphisasi. Kompilator membaca pemanggilan notify(&article) dan notify(&tweet), lalu menuliskan dua salinan fungsi khusus — satu versi dengan T diganti NewsArticle, satu versi dengan T diganti Tweet. Di dalam setiap salinan, pemanggilan item.summarize() sudah dikunci ke blok impl milik tipe tersebut; tidak ada keputusan yang tersisa untuk saat program berjalan.
Diagram di atas merangkum alurnya: satu definisi sumber beranak menjadi salinan-salinan konkret, dan setiap salinan memanggil method-nya melalui alamat yang sudah pasti. Cara pengikatan langsung ini disebut static dispatch — “dispatch” berarti memilih method mana yang dijalankan, dan “statis” berarti pilihan itu selesai sebelum program berjalan. Konsekuensinya nyata: kecepatan pemanggilan sama dengan fungsi non-generik yang ditulis tangan per tipe. Rust juga punya jalur dynamic dispatch lewat trait object dyn Trait yang menunda pemilihan method hingga program berjalan dengan bantuan tabel method (vtable) — mekanisme itu dibahas tuntas pada section Polimorfisme, dan perbedaan biaya keduanya akan dibandingkan di sana.
Pola yang barusan kita bangun bukan barang mewah — pustaka standar sendiri hidup darinya. Macro println! pada dasarnya adalah fungsi generik yang membatasi parameter formatnya dengan trait Display; angka dan teks bisa dicetak karena i32 dan String mengimplementasikan Display, sementara struct buatan kita ditolak persis dengan E0277 sampai implementasi diberikan. Demikian pula + pada chapter akhir section ini: operator itu adalah method milik trait Add, dan angka memenuhinya secara bawaan. Dengan sudut pandang ini, trait bound bukan fitur eksotis melainkan cara Rust berbicara tentang kemampuan di semua tempat — dari pencetakan sampai penjumlahan.
Saat Bound Tidak Terpenuhi #
Kekuatan bound paling terasa saat bound tersebut menolak. Kita tambahkan tipe ketiga — VideoClip — yang belum mengimplementasikan Summary, lalu mencoba mengirimkannya ke notify:
trait Summary {
fn summarize(&self) -> String;
}
struct NewsArticle {
author: String,
headline: String,
content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{} menulis \"{}\"", self.author, self.headline)
}
}
struct VideoClip {
title: String,
duration_seconds: u32,
}
fn notify<T: Summary>(item: &T) {
println!("Kabar terbaru! {}", item.summarize());
}
fn main() {
let clip = VideoClip {
title: String::from("Tur Kampus Universitas Indonesia"),
duration_seconds: 180,
};
notify(&clip);
}Pesan error kompilator:
error[E0277]: the trait bound `VideoClip: Summary` is not satisfied
--> main.rs:32:12
|
32 | notify(&clip);
| ------ ^^^^^ unsatisfied trait bound
| |
| required by a bound introduced by this call
|
help: the trait `Summary` is not implemented for `VideoClip`
--> main.rs:17:1
|
17 | struct VideoClip {
| ^^^^^^^^^^^^^^^^
help: the trait `Summary` is implemented for `NewsArticle`
--> main.rs:11:1
|
11 | impl Summary for NewsArticle {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
note: required by a bound in `notify`
--> main.rs:22:14
|
22 | fn notify<T: Summary>(item: &T) {
| ^^^^^^^ required by this bound in `notify`
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0277`.Pesan E0277 adalah bentuk penolakan paling jujur dari sistem bound. Judulnya menyebut tipe yang gagal (VideoClip) dan trait yang tidak terpenuhi (Summary). Bantuan pertama menunjuk definisi VideoClip sebagai tempat impl belum ada; bantuan kedua bahkan memamerkan contoh tipe yang berhasil (NewsArticle) sebagai pembanding. Catatan terakhir mengembalikan kita ke sumber tuntutan: bound pada baris deklarasi notify. Perbaikannya hanya satu arah — menuliskan impl Summary for VideoClip — karena fungsi generik dengan bound tidak akan pernah melonggarkan syaratnya demi satu pemanggil.
Sifat satu arah inilah yang membedakan pemeriksaan bound dari kompromi runtime pada bahasa lain. Tidak ada jalur “coba panggil, kalau gagal abaikan”: pemeriksaan terjadi sekali di awal, untuk seluruh pemanggilan, sebelum program memperoleh kesempatan berjalan. Kesalahan tipe seperti mengirim video ke notifikasi ringkasan tertangkap pada saat kompilasi, lengkap dengan lokasi pemanggilan dan lokasi bound yang menuntut — dua petunjuk yang membuat perbaikan cepat dilakukan.
Ringkasan #
Trait bound mengubah fungsi generik dari sekadar “menerima tipe apa pun” menjadi “menerima tipe apa pun yang mengimplementasikan trait tertentu”. Deklarasi <T: Summary> membuka izin memanggil method kontrak di dalam body, monomorphisasi melahirkan salinan fungsi per tipe konkret, dan static dispatch mengikat setiap pemanggilan method pada saat kompilasi tanpa biaya runtime. Saat argumen tidak memenuhi bound, E0277 menolaknya sambil menunjuk tipe, trait, dan lokasi bound yang menuntut. Penulisan <T: Summary> menuntut kita menyiapkan dua nama — T dan bound-nya — padahal untuk fungsi sederhana yang hanya menerima satu nilai, Rust menyediakan bentuk yang lebih ringkas. Bentuk itulah pembahasan chapter berikutnya.