Satu trait bound menyelesaikan satu kebutuhan — PartialOrd untuk membandingkan, Display untuk mencetak. Fungsi dunia nyata kerap membutuhkan beberapa jaminan sekaligus pada type parameter yang sama, ditambah beberapa type parameter dengan syarat masing-masing. Notasi deklaratifnya sederhana — tanda plus menggabungkan bound — tetapi tanda tangan cepat memanjang dan memendam nama parameter di balik daftar trait. Rust menyediakan klausa where untuk memindahkan seluruh syarat ke baris tersendiri. Chapter ini menyusun multiple bounds, membuktikan bahwa notasi inline dan where identik bagi kompilator, lalu menutup dengan kaidah pemilihan di antara keduanya.

Dua Syarat dalam Satu Bound #

Kita perluas fungsi laporan dari Chapter Trait Bound: selain menemukan nilai terbesar, fungsinya harus mencetaknya langsung dengan label. Pembandingan menuntut PartialOrd; pencetakan dengan formatter {} menuntut Display. Kedua trait digabung dengan tanda plus:

rust
use std::fmt::Display;

fn report_largest<T: PartialOrd + Display>(label: &str, list: &[T]) {
    let mut largest = &list[0];
    for item in list {
        if item > largest {
            largest = item;
        }
    }
    println!("{} terbesar: {}", label, largest);
}

fn main() {
    let scores = [72, 85, 90, 68];
    let ratings = [4.2, 3.8, 4.9, 4.5];

    report_largest("Skor", &scores);
    report_largest("Rating", &ratings);
}

Output yang Dihasilkan:

text
Skor terbesar: 90
Rating terbesar: 4.9

Mari kita bedah kode di atas baris per baris:

  1. Baris 1 (use std::fmt::Display;): PartialOrd tinggal di prelude sehingga bisa dipakai langsung, sedangkan Display harus diimpor secara eksplisit dari std::fmt — kebiasaan yang juga berlaku untuk Debug.
  2. Baris 3 (fn report_largest<T: PartialOrd + Display>(label: &str, list: &[T])): bound T: PartialOrd + Display terbaca sebagai “T harus mengimplementasikan PartialOrd DAN Display”. Tanda plus berarti kedua syarat wajib terpenuhi — bukan salah satu.
  3. Baris 6 (if item > largest {): pembandingan sah karena PartialOrd tercantum di bound — izin yang sama dengan fungsi largest pada chapter sebelumnya.
  4. Baris 10 (println!("{} terbesar: {}", label, largest);): pencetakan largest bertipe &T sah karena Display tercantum di bound — izin yang sama dengan fungsi announce yang dulu ditolak E0277.
  5. Baris 17-18 (dua pemanggilan): T = i32 lalu T = f64 — keduanya mengimplementasikan PartialOrd dan Display, sehingga melintasi bound gabungan tanpa hambatan.

Klausa where untuk Tanda Tangan yang Panjang #

Notasi plus mulai menyulitkan begitu type parameter berjumlah lebih dari satu. Bayangkan tanda tangan dengan dua parameter — T untuk pembacaan sensor yang harus bisa dibandingkan sekaligus dicetak, dan U untuk satuan yang cukup dicetak. Ditulis inline, syarat-syarat itu menumpuk di awal deklarasi dan memendam nama parameter di belakang tumpukan trait. Klausa where memindahkan seluruh syarat ke bawah, satu baris per type parameter. Program berikut memuat kedua notasi sekaligus agar kita bisa membandingkannya secara langsung:

rust
use std::fmt::Display;

fn report_highs_inline<T: PartialOrd + Display, U: Display>(
    label: &str,
    readings: &[T],
    unit: &U,
) -> String {
    let mut highest = &readings[0];
    for reading in readings {
        if reading > highest {
            highest = reading;
        }
    }
    format!("{} tertinggi: {} {}", label, highest, unit)
}

fn report_highs_where<T, U>(label: &str, readings: &[T], unit: &U) -> String
where
    T: PartialOrd + Display,
    U: Display,
{
    report_highs_inline(label, readings, unit)
}

fn main() {
    let temperatures = [29.9, 31.5, 30.2, 33.8];
    let humidity = [62, 65, 58, 71];

    println!("{}", report_highs_inline("Suhu", &temperatures, &"derajat"));
    println!("{}", report_highs_where("Kelembapan", &humidity, &"%"));
}

Output yang Dihasilkan:

text
Suhu tertinggi: 33.8 derajat
Kelembapan tertinggi: 71 %

Mari kita bedah kode di atas baris per baris:

  1. Baris 3-7 (fn report_highs_inline<T: PartialOrd + Display, U: Display>(...)): bentuk inline — semua bound menempel pada daftar type parameter. Dengan dua parameter dan bound gabungan, baris pertama deklarasi menjadi padat; parameter label, readings, dan unit terdorong ke baris-baris berikutnya.
  2. Baris 17-21 (fn report_highs_where<T, U>(...) where T: PartialOrd + Display, U: Display,): bentuk where — klausa ditempatkan setelah daftar parameter, sebelum kurung kurawal body. Setiap type parameter mendapat barisnya sendiri; daftar syarat dipisah koma. Membaca tanda tangan menjadi dua langkah yang tenang: dulu parameternya, baru syaratnya.
  3. Baris 22-23 (body report_highs_where): fungsi bentuk where hanya meneruskan pekerjaan ke fungsi bentuk inline. Penerusan ini berhasil justru karena kedua notasi identik di mata kompilator — syarat T: PartialOrd + Display dan U: Display berdiri sama kuat di kedua fungsi, sehingga bound fungsi pemanggil memenuhi bound fungsi yang dipanggil.
  4. Baris 29-30 (dua pemanggilan): report_highs_inline dipanggil dengan T = f64 dan U = &str; report_highs_where dipanggil dengan T = i32 dan U = &str. Kedua jalur menghasilkan format laporan yang sama persis — bukti perilaku yang tidak berubah oleh pilihan notasi.

where juga Hidup di Blok impl #

Klausa where bukan milik fungsi saja. Blok impl untuk struct generik — yang kita pelajari pada Chapter Method Generik — boleh memakai where untuk bound yang panjang, dan bound tersebut menyempitkan instansiasi mana yang memiliki method di dalamnya:

rust
use std::fmt::Display;

struct Locker<T> {
    content: T,
}

impl<T> Locker<T>
where
    T: Display,
{
    fn show(&self) {
        println!("Isi loker: {}", self.content);
    }
}

fn main() {
    let locker = Locker { content: 4582 };
    locker.show();
}

Output yang Dihasilkan:

text
Isi loker: 4582

Mari kita bedah bagian penting kode di atas:

  1. Baris 7-10 (impl<T> Locker<T> where T: Display,): klausa where ditempel setelah tipe yang di-impl-kan. Seluruh method di blok ini hanya tersedia untuk instansiasi yang kontennya mengimplementasikan Display.
  2. Baris 11-13 (fn show(&self)): pencetakan self.content sah tanpa bound pada method — bound sudah dinyatakan satu kali di level impl dan mengikat semua method di dalamnya.
  3. Baris 17 (let locker = Locker { content: 4582 };): i32 mengimplementasikan Display, jadi Locker<i32> berhak memanggil show.

Ringkasan #

Tanda plus menggabungkan trait bound sebagai syarat yang harus terpenuhi semuanya — T: PartialOrd + Display membuka pembandingan dan pencetakan sekaligus pada satu type parameter, sementara type parameter lain membawa daftar syaratnya sendiri. Klausa where memindahkan seluruh daftar syarat ke bawah tanda tangan, dan program kita membuktikan notasi inline maupun where diperlakukan identik oleh kompilator hingga ke penerusan antar-fungsi. Blok impl generik juga bisa memakai where untuk mengikat semua method dalam satu syarat bersama. Sebelum menutup section, masih ada satu pertanyaan yang berkali-kali kita singgung dan belum dijawab tuntas: apa sebenarnya yang dihasilkan kompilator dari semua kode generik ini? Chapter Monomorphisasi menjawabnya sampai ke isi berkas biner.