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:

rust
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:

text
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:

rust
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:

text
Skor tertinggi: 90
Rating tertinggi: 4.9
Nama terbesar: Siti

Deklarasi <T: PartialOrd> terbaca sebagai “fungsi ini menerima T apa pun, asalkan T mengimplementasikan PartialOrd”. Mari kita bedah kode di atas baris per baris:

  1. Baris 1 (fn largest<T: PartialOrd>(list: &[T]) -> &T {): bound T: PartialOrd mempersempit dunia T menjadi tipe-tipe yang bisa diperbandingkan. PartialOrd tersedia tanpa use karena prelude memuatnya.
  2. Baris 4 (if item > largest {): operasi yang tadinya ditolak kini sah — bound menjamin PartialOrd tersedia, dan pembandingan &T dengan &T diizinkan karena PartialOrd juga diimplementasikan untuk reference ke tipe yang PartialOrd.
  3. Baris 12-14 (tiga koleksi): scores bertipe [i32; 4], ratings [f64; 4], names [String; 3] — ketiganya mengimplementasikan PartialOrd.
  4. Baris 16-18 (tiga pemanggilan): T terikat berturut-turut menjadi i32, f64, dan String. Untuk String, pembandingan berjalan leksikografis per karakter dari kiri: "Siti" unggul atas "Budi" dan "Andi" karena huruf pertamanya paling besar, sehingga hasilnya Siti.

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:

rust
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:

text
Konten baru: 'Rust Kian Populer' dibaca 1200 kali
Konten baru: 'Tur Rust 10 Menit' ditonton 3400 kali

Mari kita bedah kode di atas baris per baris:

  1. Baris 1-3 (trait Summary dan fn summarize(&self)): trait mendeklarasikan kontrak — “semua yang mengimplementasikan Summary wajib menyediakan summarize”. Trait dibahas tuntas di Section Traits; untuk kebutuhan chapter ini, cukup pandang trait sebagai daftar janji method.
  2. 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 field title dan views, tetapi teks ringkasnya berbeda per jenis konten.
  3. 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 dengan PartialOrd pada largest.
  4. Baris 37-38 (dua pemanggilan): T terikat menjadi Article lalu Video; satu fungsi notify melayani 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:

rust
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:

text
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:

rust
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:

text
- Product { name: "Kopi Gayo" }
- Order { id: 101 }
Produk unggulan: Kopi Gayo
Pesanan diambil: ORD-101

Dua 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.