Pada chapter sebelumnya kita berhasil menerbitkan kontrak Summary, tetapi pemanggilan article.summarize() masih ditolak dengan E0599 karena belum ada tipe yang menandatanganinya. Chapter ini menutup lubang tersebut dengan bentuk khusus blok impl: bukan lagi impl NewsArticle seperti method biasa di section Struktur Data, melainkan impl Summary for NewsArticle — “implementasikan trait Summary untuk tipe NewsArticle”. Di dalam blok itu kita menuliskan method yang sama persis dengan yang kita kenal sebelumnya, hanya saja kini setiap method memenuhi satu baris kontrak. Begitu kontrak terpenuhi, method trait menjadi bisa dipanggil lewat titik seperti method biasa, dan kompilator mengikat pemanggilan tersebut langsung ke implementasi tipe tersebut saat kompilasi.

Sintaks impl Trait for Type #

Mari kita ambil kontrak dua-method dari chapter sebelumnya dan penuhi untuk kedua tipe aplikasi berita kita:

rust
trait Summary {
    fn summarize(&self) -> String;
    fn preview(&self) -> String;
}

struct NewsArticle {
    author: String,
    headline: String,
    content: String,
}

struct Tweet {
    username: String,
    content: String,
}

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{} menulis \"{}\": {}", self.author, self.headline, self.content)
    }

    fn preview(&self) -> String {
        format!("{} - {}", self.headline, self.author)
    }
}

impl Summary for Tweet {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }

    fn preview(&self) -> String {
        format!("{} baru saja berkicau", self.username)
    }
}

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."),
    };

    let tweet = Tweet {
        username: String::from("budi_perjalanan"),
        content: String::from("Sampai di puncak Gunung Bromo! Matahari terbit luar biasa indah."),
    };

    println!("Ringkasan artikel: {}", article.summarize());
    println!("Pratinjau artikel: {}", article.preview());
    println!("Ringkasan cuitan: {}", tweet.summarize());
    println!("Pratinjau cuitan: {}", tweet.preview());
}

Output yang Dihasilkan:

text
Ringkasan artikel: Rina Wijaya menulis "Pasar Saham Jakarta Ditutup Menguat": Indeks naik 1,2 persen.
Pratinjau artikel: Pasar Saham Jakarta Ditutup Menguat - Rina Wijaya
Ringkasan cuitan: budi_perjalanan: Sampai di puncak Gunung Bromo! Matahari terbit luar biasa indah.
Pratinjau cuitan: budi_perjalanan baru saja berkicau

Mari kita bedah bagian-bagian pentingnya:

  1. Baris 17 (impl Summary for NewsArticle {): kata kunci impl kini diikuti nama trait, kata kunci for, lalu nama tipe. Baris ini menyatakan bahwa NewsArticle menandatangani kontrak Summary — sejak baris ini, NewsArticle adalah tipe yang mengimplementasikan Summary.
  2. Baris 19 (format!("{} menulis \"{}\": {}", ...)): method summarize ditulis dengan body penuh, persis seperti method pada blok impl biasa. Parameter &self berjenis &NewsArticle secara otomatis, sehingga field author, headline, dan content dapat dibaca langsung.
  3. Baris 27 (impl Summary for Tweet {): blok kedua mengikat kontrak yang sama pada tipe yang berbeda. Satu trait boleh diimplementasikan oleh banyak tipe, dan setiap tipe menulis perilakunya sendiri — di sinilah letak perbedaan hasil antara ringkasan artikel dan ringkasan cuitan pada output.
  4. Baris 49 (println!("Ringkasan artikel: {}", article.summarize());): pemanggilan article.summarize() memakai sintaks titik yang sama dengan method biasa. Tidak ada kata kunci khusus saat memanggil method trait — pemanggil bahkan tidak perlu tahu bahwa summarize berasal dari trait.
  5. Baris 51 (println!("Ringkasan cuitan: {}", tweet.summarize());): nama method yang sama, summarize, kini punya dua perilaku berbeda sesuai tipe pemanggilnya. Kemampuan satu nama dengan banyak wujud inilah fondasi abstraksi di Rust.

Perhatikan pula satu ketentuan yang membedakan trait dari method biasa: tipe Tweet sama sekali tidak punya field author atau headline, tetapi blok impl Summary for Tweet tetap sah karena method di dalamnya hanya memakai field milik Tweet sendiri. Kontrak menuntut signature, bukan bentuk data.

Satu tipe juga tidak terbatas pada satu trait. Sebuah struct boleh mengimplementasikan Summary, Display, dan Clone sekaligus dengan menuliskan beberapa blok impl terpisah, masing-masing dengan pasangan trait-tipenya sendiri; kemampuan-kemampuan itu kemudian menumpuk pada tipe yang sama dan dapat dipakai bergantian di tempat berbeda. Pola bertumpuk inilah yang dipakai String dari pustaka standar — tipe tersebut mengimplementasikan puluhan trait dari Display hingga Clone — dan yang membuat #[derive(Debug, Clone, PartialEq, Default)] pada chapter Derive Macro dapat melengkapi struct kita dengan empat kemampuan sekaligus.

Method Resolution Saat Pemanggilan #

Saat kompilator membaca article.summarize(), proses yang berjalan disebut method resolution: kompilator mencari method bernama summarize yang penerima (receiver) cocok dengan tipe NewsArticle. Pencarian menemukannya di blok impl Summary for NewsArticle, dan pemanggilan tersebut dikunci ke sana — sudah selesai saat kompilasi, tanpa pencarian saat program berjalan. Pemanggilan tweet.summarize() diperiksa dengan cara yang sama dan mengarah ke blok impl milik Tweet.

Proses resolusi ini juga menjelaskan satu syarat yang tampak sepele tetapi sering menjegal pemula: trait harus berada dalam scope agar method-nya dapat dipanggil. Trait Summary kita lokal pada satu file sehingga otomatis terlihat, tetapi saat kita mulai memakai trait dari pustaka standar — misalnya Add pada chapter Operator Overload nanti — baris use std::ops::Add; menjadi wajib sebelum operator + pada tipe buatan kita dikenali. Tanpa trait di dalam scope, kompilator menolak pemanggilan dengan E0599 seolah method itu tidak ada. Perilaku ini terasa aneh di awal, tetapi tujuannya melindungi kejelasan: dua trait berbeda boleh punya method bernama sama, dan scope-lah yang menentukan method mana yang sedang kita maksud.

Perlu dicatat pula bahwa pemanggil article.summarize() sama sekali tidak perlu tahu summarize berasal dari trait. Dari sisi pemanggil, method hasil implementasi trait dan method biasa dari blok impl NewsArticle terlihat dan terpanggil dengan cara yang persis sama — sintaks titik tanpa upacara. Perbedaan keduanya baru terasa saat kita mulai menuntut kemampuan secara generik, dan tuntutan itulah materi chapter Trait Bound.

Kontrak Harus Dipenuhi Utuh #

Kontrak trait bersifat take-it-or-leave-it: begitu sebuah tipe menyatakan impl Summary for Tweet, seluruh method pada daftar kontrak harus tersedia. Lupakan satu saja, kompilator menolak program sebelum berjalan:

rust
trait Summary {
    fn summarize(&self) -> String;
    fn preview(&self) -> String;
}

struct Tweet {
    username: String,
    content: String,
}

impl Summary for Tweet {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

fn main() {
    let tweet = Tweet {
        username: String::from("@budi_perjalanan"),
        content: String::from("Sampai di puncak Gunung Bromo!"),
    };

    println!("{}", tweet.summarize());
}

Pesan error kompilator:

text
error[E0046]: not all trait items implemented, missing: `preview`
  --> main.rs:11:1
   |
 3 |     fn preview(&self) -> String;
   |     ---------------------------- `preview` from trait
...
11 | impl Summary for Tweet {
   | ^^^^^^^^^^^^^^^^^^^^^^ missing `preview` in implementation

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0046`.

Pesan E0046 menyoroti dua lokasi sekaligus. Anotasi pada baris 3 menunjuk preview di dalam definisi trait sebagai bagian kontrak yang tertinggal, sedangkan tanda pada baris 11 menunjuk blok impl Summary for Tweet sebagai pihak yang belum menyelesaikan kewajibannya. Kompilator bahkan menuliskan nama item yang hilang secara eksplisit pada judul error — missing: 'preview' — sehingga perbaikannya tinggal menambahkan method preview ke dalam blok impl.

Ringkasan #

Blok impl Trait for Type adalah pena tanda tangan kontrak: di dalamnya setiap signature dari trait ditulis ulang beserta body khusus untuk tipe tersebut, dan seluruhnya wajib hadir — satu saja kurang, E0046 menghentikan kompilasi. Setelah kontrak terpenuhi, method trait dipanggil dengan sintaks titik biasa, dan method resolution mengunci setiap pemanggilan pada implementasi milik tipe tersebut pada saat kompilasi. Satu trait kini hidup pada dua tipe dengan dua perilaku berbeda. Persoalan berikutnya langsung terasa pada skala: jika mayoritas tipe akan menulis perilaku summarize yang sama, mengapa setiap blok impl harus menuliskannya dari nol? Jawabannya adalah default implementation.