Chapter Fungsi Generik ditutup dengan satu kegagalan: T polos tidak bisa dicetak, dibandingkan, atau dijumlahkan. Sekarang kita hadapi akar masalahnya. Di Rust, operasi bukan kemampuan bawaan tipe — operator > didefinisikan oleh trait PartialOrd, formatter {} oleh trait Display, penjumlahan oleh trait Add. Agar operasi tersedia pada T, kita harus menjanjikannya lewat trait bound pada deklarasi. Chapter ini menghidupkan kembali fungsi largest dari Chapter Masalah Duplikasi Kode dengan bound T: PartialOrd, memperkenalkan bound dengan trait buatan sendiri, lalu membedah dua arah kegagalan: definisi yang menuntut terlalu banyak dan pemanggil yang tak memenuhi syarat.
Kompilator Menolak Operasi Tanpa Jaminan #
Kita mulai dari niat yang bersih: menulis largest generik untuk menggantikan fungsi-fungsi kembar. Versi pertama ini ditolak mentah:
fn largest<T>(list: &[T]) -> &T {
let mut largest = &list[0];
for item in list {
if item > largest {
largest = item;
}
}
largest
}
fn main() {
let scores = [72, 85, 90];
println!("Terbesar: {}", largest(&scores));
}Pesan error kompilator:
error[E0369]: binary operation `>` cannot be applied to type `&T`
--> main.rs:4:17
|
4 | if item > largest {
| ---- ^ ------- &T
| |
| &T
help: consider restricting type parameter `T` with trait `PartialOrd`
|
1 | fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> &T {
| ++++++++++++++++++++++
error: aborting due to 1 previous error.
For more information about this error, try `rustc --explain E0369`.Pesan E0369 memaparkan dua fakta. Pertama, operator > tidak bisa diterapkan pada &T karena pembandingan terurut bukan kemampuan universal — struct Order yang kita definisikan sendiri, misalnya, tidak punya aturan “lebih besar dari” apa pun. Kedua, kompilator langsung menyarankan bound T: PartialOrd — trait yang mendefinisikan pembandingan terurut sebagian. Penolakan terjadi di titik definisi, bukan menunggu pemanggilan: body harus sah untuk semua T yang diperbolehkan, dan tanpa bound, “semua T” memuat tipe-tipe yang tak bisa dibandingkan.
Menyatakan Trait Bound pada Deklarasi #
Kita ikuti saran kompilator dan tambahkan T: PartialOrd di antara nama fungsi dan parameter:
fn largest<T: PartialOrd>(list: &[T]) -> &T {
let mut largest = &list[0];
for item in list {
if item > largest {
largest = item;
}
}
largest
}
fn main() {
let scores = [72, 85, 90, 68];
let ratings = [4.2, 3.8, 4.9, 4.5];
let names = [String::from("Budi"), String::from("Siti"), String::from("Andi")];
println!("Skor tertinggi: {}", largest(&scores));
println!("Rating tertinggi: {}", largest(&ratings));
println!("Nama terbesar: {}", largest(&names));
}Output yang Dihasilkan:
Skor tertinggi: 90
Rating tertinggi: 4.9
Nama terbesar: SitiDeklarasi <T: PartialOrd> terbaca sebagai “fungsi ini menerima T apa pun, asalkan T mengimplementasikan PartialOrd”. Mari kita bedah kode di atas baris per baris:
- Baris 1 (
fn largest<T: PartialOrd>(list: &[T]) -> &T {): boundT: PartialOrdmempersempit duniaTmenjadi tipe-tipe yang bisa diperbandingkan.PartialOrdtersedia tanpausekarena prelude memuatnya. - Baris 4 (
if item > largest {): operasi yang tadinya ditolak kini sah — bound menjaminPartialOrdtersedia, dan pembandingan&Tdengan&Tdiizinkan karenaPartialOrdjuga diimplementasikan untuk reference ke tipe yangPartialOrd. - Baris 12-14 (tiga koleksi):
scoresbertipe[i32; 4],ratings[f64; 4],names[String; 3]— ketiganya mengimplementasikanPartialOrd. - Baris 16-18 (tiga pemanggilan):
Tterikat berturut-turut menjadii32,f64, danString. UntukString, pembandingan berjalan leksikografis per karakter dari kiri:"Siti"unggul atas"Budi"dan"Andi"karena huruf pertamanya paling besar, sehingga hasilnyaSiti.
Fungsi announce yang gagal di Chapter Fungsi Generik kini punya obat yang bentuknya sama: fn announce<T: std::fmt::Display>(value: T) — persis saran kompilator pada E0277 waktu itu. Pola T: Trait adalah satu-satunya cara membuka operasi pada type parameter.
Bagaimana Bound Diperiksa #
Pemeriksaan bound berlangsung sepenuhnya pada compile time dan bekerja di dua arah. Arah pertama: saat kompilator membaca definisi largest, setiap operasi pada T dicocokkan dengan bound — > menuntut PartialOrd, dan bound menyediakannya, maka body dinyatakan sah untuk semua T yang lolos syarat. Arah kedua: pada setiap pemanggilan, kompilator memeriksa bahwa tipe konkret pemanggil mengimplementasikan trait yang diminta — misalnya largest(&scores) menuntut i32: PartialOrd, dan implementasi tersebut memang ada di standard library.
Dua arah ini menjelaskan mengapa tidak ada biaya runtime: begitu tipe terikat dan bound terpenuhi, kompilator tahu persis method mana yang dipanggil, sehingga pemanggilan disusun sebagai panggilan langsung ke alamat kode — mekanisme static dispatch yang akan kita buktikan di Chapter Monomorphisasi. Bound bukan pemeriksaan runtime seperti validasi data; bound adalah kontrak yang diverifikasi sebelum program lahir.
Bound dengan Trait Buatan Sendiri #
PartialOrd dan Display adalah trait standard library, tetapi bound bekerja sama persis dengan trait yang kita definisikan sendiri. Berikut trait kecil bernama Summary dengan satu method:
trait Summary {
fn summarize(&self) -> String;
}
struct Article {
title: String,
views: u32,
}
struct Video {
title: String,
views: u32,
}
impl Summary for Article {
fn summarize(&self) -> String {
format!("'{}' dibaca {} kali", self.title, self.views)
}
}
impl Summary for Video {
fn summarize(&self) -> String {
format!("'{}' ditonton {} kali", self.title, self.views)
}
}
fn notify<T: Summary>(item: &T) {
println!("Konten baru: {}", item.summarize());
}
fn main() {
let article = Article { title: String::from("Rust Kian Populer"), views: 1200 };
let video = Video { title: String::from("Tur Rust 10 Menit"), views: 3400 };
notify(&article);
notify(&video);
}Output yang Dihasilkan:
Konten baru: 'Rust Kian Populer' dibaca 1200 kali
Konten baru: 'Tur Rust 10 Menit' ditonton 3400 kaliMari kita bedah kode di atas baris per baris:
- Baris 1-3 (
trait Summarydanfn summarize(&self)): trait mendeklarasikan kontrak — “semua yang mengimplementasikanSummarywajib menyediakansummarize”. Trait dibahas tuntas di Section Traits; untuk kebutuhan chapter ini, cukup pandang trait sebagai daftar janji method. - Baris 15 dan 23 (
impl Summary for Article/impl Summary for Video): kedua struct memenuhi kontrak dengan cara masing-masing. Judul dan jumlah tayang sama-sama fieldtitledanviews, tetapi teks ringkasnya berbeda per jenis konten. - Baris 31 (
fn notify<T: Summary>(item: &T)): bound kini memakai trait buatan sendiri. Di dalam body,item.summarize()sah karena bound menjanjikan method tersebut — mekanisme yang identik denganPartialOrdpadalargest. - Baris 37-38 (dua pemanggilan):
Tterikat menjadiArticlelaluVideo; satu fungsinotifymelayani keduanya tanpa peduli jenis kontennya.
Pemanggil dengan Tipe yang Tak Memenuhi Bound #
Sisi kedua kegagalan menimpa pemanggil, bukan definisi. Fungsi berikut sah dan sehat, tetapi salah satu pemanggilnya menabrak bound:
use std::fmt::Debug;
#[derive(Debug)]
struct Product {
name: String,
}
fn show_all<T: Debug>(items: &[T]) {
for item in items {
println!("- {:?}", item);
}
}
struct Order {
id: u32,
}
fn main() {
let products = [Product { name: String::from("Kopi Gayo") }];
let orders = [Order { id: 101 }];
show_all(&products);
show_all(&orders);
}Pesan error kompilator:
error[E0277]: `Order` doesn't implement `Debug`
--> main.rs:23:14
|
23 | show_all(&orders);
| -------- ^^^^^^^ the trait `Debug` is not implemented for `Order`
| |
| required by a bound introduced by this call
|
note: required by a bound in `show_all`
--> main.rs:8:16
|
8 | fn show_all<T: Debug>(items: &[T]) {
| ^^^^^^^ required by this bound in `show_all`
help: consider annotating `Order` with `#[derive(Debug)]`
|
14 + #[derive(Debug)]
15 | struct Order {
|
error: aborting due to 1 previous error.
For more information about this error, try `rustc --explain E0277`.Kali ini definisi show_all lolos pemeriksaan — bound membuat body-nya sah. Yang ditolak adalah pemanggilan show_all(&orders): Order tidak mengimplementasikan Debug. Perhatikan pembelaan Product pada pemanggilan sebelumnya — #[derive(Debug)] di baris 4 membuatnya lolos. Perbaikan untuk Order bahkan dituliskan kompilator: tambahkan #[derive(Debug)]. Program utuh versi perbaikannya hanya berubah dua baris:
use std::fmt::Debug;
#[derive(Debug)]
struct Product {
name: String,
}
fn show_all<T: Debug>(items: &[T]) {
for item in items {
println!("- {:?}", item);
}
}
#[derive(Debug)]
struct Order {
id: u32,
}
fn main() {
let products = [Product { name: String::from("Kopi Gayo") }];
let orders = [Order { id: 101 }];
show_all(&products);
show_all(&orders);
println!("Produk unggulan: {}", products[0].name);
println!("Pesanan diambil: ORD-{}", orders[0].id);
}Output yang Dihasilkan:
- Product { name: "Kopi Gayo" }
- Order { id: 101 }
Produk unggulan: Kopi Gayo
Pesanan diambil: ORD-101Dua baris println! terakhir pada main membaca field name dan id — kebiasaan yang sekaligus memastikan kedua struct benar-benar dipakai di luar formatter. Dengan satu baris #[derive(Debug)], Order resmi melintasi bound, dan kedua pemanggilan show_all kini diterima.
Ringkasan #
Trait bound T: Trait membuka operasi pada type parameter: > menuntut PartialOrd, pencetakan {} menuntut Display, dan method apa pun yang kita janjikan lewat trait buatan sendiri seperti Summary bisa dipanggil di dalam body generik. Kompilator memeriksa kontrak ini dua arah pada compile time — body harus sah untuk semua T yang diizinkan bound (E0369 bila melanggar), dan tipe konkret tiap pemanggilan harus mengimplementasikan trait yang diminta (E0277 bila gagal). Satu fungsi kerap butuh beberapa jaminan sekaligus, dan Chapter Multiple Bounds dan where menyusunnya dengan notasi + beserta bentuk yang lebih rapi dibaca.