Trait di Rust
Trait (sifat atau kemampuan bersama) mendefinisikan sekumpulan perilaku yang bisa diimplementasikan oleh sebuah tipe, mirip interface di bahasa lain. Dengan trait, Rust menyatakan “tipe ini bisa melakukan X”, dan trait jugalah yang ada di balik trait bound yang kamu lihat di halaman Generik.
Mendefinisikan trait
Section titled “Mendefinisikan trait”trait Speak { fn speak(&self) -> String;}Artinya: setiap tipe yang mengimplementasikan Speak wajib menyediakan method speak yang mengembalikan String. Sendirian, trait hanyalah sebuah janji. Ia tidak melakukan apa-apa sampai ada tipe yang mengimplementasikannya.
Mengimplementasikan trait
Section titled “Mengimplementasikan trait”trait Speak { fn speak(&self) -> String;}
struct Dog;struct Cat;
impl Speak for Dog { fn speak(&self) -> String { String::from("Guk guk!") }}
impl Speak for Cat { fn speak(&self) -> String { String::from("Meong!") }}
fn main() { let dog = Dog; let cat = Cat;
println!("{}", dog.speak()); println!("{}", cat.speak());}Output:
Guk guk!Meong!Dog dan Cat adalah dua tipe yang tidak berhubungan, tapi keduanya mengimplementasikan Speak, jadi keduanya bisa dipanggil dengan .speak(). Polanya sama dengan blok impl di halaman Struct. impl Speak for Dog cuma berarti “begini cara Dog memenuhi trait Speak.”
Trait sebagai parameter fungsi
Section titled “Trait sebagai parameter fungsi”Inilah manfaat sebenarnya: kamu bisa menulis fungsi yang menerima tipe apa saja yang mengimplementasikan sebuah trait, tanpa peduli tipe spesifiknya:
trait Speak { fn speak(&self) -> String;}
struct Dog;impl Speak for Dog { fn speak(&self) -> String { String::from("Guk guk!") }}
fn announce(animal: &impl Speak) { println!("Hewan itu berkata: {}", animal.speak());}
fn main() { let dog = Dog; announce(&dog);}animal: &impl Speak berarti “tipe apa pun yang mengimplementasikan Speak, aku tidak perlu tahu yang mana.” Tambahkan tipe Bird yang juga mengimplementasikan Speak, dan announce langsung bisa dipakai untuknya juga, tanpa perubahan apa pun.
Trait yang sudah pernah kamu pakai
Section titled “Trait yang sudah pernah kamu pakai”Ingat #[derive(Debug)] dari halaman Struct? Debug sendiri adalah trait. derive hanya membuatkan implementasinya secara otomatis, jadi kamu tidak perlu menulisnya sendiri. Trait lain yang sering akan kamu temui: Clone (untuk .clone()), PartialEq (untuk ==), dan PartialOrd (untuk < dan >, yang sudah kamu lihat sebagai trait bound di halaman Generik).
Implementasi default
Section titled “Implementasi default”Trait boleh menyediakan isi method default, yang bisa dipakai apa adanya atau ditimpa (override) oleh tipe yang mengimplementasikannya:
trait Greet { fn name(&self) -> String;
fn greet(&self) -> String { format!("Halo, {}!", self.name()) }}Tipe apa pun yang mengimplementasikan Greet cukup mendefinisikan name(). greet() didapat gratis, kecuali tipe itu memilih untuk menimpanya juga.
Masih banyak hal tentang trait yang belum dibahas di halaman ini. Halaman Trait yang sering dipakai membahas trait standar yang akan kamu pakai sehari-hari, dan Trait lanjutan membahas associated type, supertrait, dan alat lain yang akan kamu temui di codebase yang lebih besar.
Latihan
Section titled “Latihan”1. Bentuk yang punya luas
Section titled “1. Bentuk yang punya luas”Definisikan trait Area dengan satu method, fn area(&self) -> f64. Implementasikan untuk Rectangle (dengan width dan height) dan Square (dengan side). Cetak luas persegi panjang 3 kali 4 dan persegi dengan sisi 5. Petunjuk: setiap blok impl Area for ... hanya butuh satu method itu, dan isinya cukup satu perkalian.
Lihat solusi
trait Area { fn area(&self) -> f64;}
struct Rectangle { width: f64, height: f64,}
struct Square { side: f64,}
impl Area for Rectangle { fn area(&self) -> f64 { self.width * self.height }}
impl Area for Square { fn area(&self) -> f64 { self.side * self.side }}
fn main() { let rect = Rectangle { width: 3.0, height: 4.0 }; let square = Square { side: 5.0 };
println!("Persegi panjang: {}", rect.area()); println!("Persegi: {}", square.area());}Output:
Persegi panjang: 12Persegi: 252. Satu fungsi untuk banyak tipe
Section titled “2. Satu fungsi untuk banyak tipe”Definisikan trait Describe dengan fn describe(&self) -> String. Implementasikan untuk Book (dengan title) dan Movie (dengan title dan minutes). Lalu tulis satu fungsi fn show(item: &impl Describe) yang mencetak deskripsinya, dan panggil dengan keduanya. Petunjuk: pakai format! untuk menyusun setiap string deskripsi.
Lihat solusi
trait Describe { fn describe(&self) -> String;}
struct Book { title: String,}
struct Movie { title: String, minutes: u32,}
impl Describe for Book { fn describe(&self) -> String { format!("Buku: {}", self.title) }}
impl Describe for Movie { fn describe(&self) -> String { format!("Film: {} ({} menit)", self.title, self.minutes) }}
fn show(item: &impl Describe) { println!("{}", item.describe());}
fn main() { let book = Book { title: String::from("Laskar Pelangi") }; let movie = Movie { title: String::from("Up"), minutes: 96 };
show(&book); show(&movie);}Output:
Buku: Laskar PelangiFilm: Up (96 menit)3. Pakai atau timpa method default
Section titled “3. Pakai atau timpa method default”Ambil trait Greet dari halaman ini, lengkap dengan method default greet(). Implementasikan untuk Person, yang tetap memakai versi default, dan untuk Pirate, yang menimpa greet() supaya mengucapkan Ahoy, ...!. Petunjuk: kedua tipe wajib mendefinisikan name(). Hanya Pirate yang juga menulis greet() sendiri.
Lihat solusi
trait Greet { fn name(&self) -> String;
fn greet(&self) -> String { format!("Halo, {}!", self.name()) }}
struct Person { name: String,}
struct Pirate { name: String,}
impl Greet for Person { fn name(&self) -> String { self.name.clone() }}
impl Greet for Pirate { fn name(&self) -> String { self.name.clone() }
fn greet(&self) -> String { format!("Ahoy, {}!", self.name()) }}
fn main() { let ana = Person { name: String::from("Ana") }; let jack = Pirate { name: String::from("Jack") };
println!("{}", ana.greet()); println!("{}", jack.greet());}Output:
Halo, Ana!Ahoy, Jack!Ringkasan
Section titled “Ringkasan”- Trait mendefinisikan perilaku yang bisa diimplementasikan oleh sebuah tipe, mirip interface.
impl TraitName for Typemenyediakan implementasinya.&impl TraitNamesebagai tipe parameter menerima tipe apa pun yang mengimplementasikan trait itu.- Trait bisa menyediakan isi method default yang boleh dipakai atau ditimpa oleh tipe yang mengimplementasikannya.
Debug,Clone, danPartialEqadalah trait umum dari standard library, dan#[derive(...)]mengimplementasikannya secara otomatis.
Kuis singkat
Section titled “Kuis singkat”Kuis singkat
1. Apa yang didefinisikan oleh trait?
2. Apa arti animal: &impl Speak sebagai tipe parameter?
3. Apa yang sebenarnya dilakukan #[derive(Debug)]?
4. Apa itu implementasi default di dalam trait?
5. Menurut halaman ini, mana yang merupakan trait, bukan struct atau enum?
Skor: 0 / 5
Bacaan lebih lanjut
Section titled “Bacaan lebih lanjut”- Traits: Defining Shared Behavior — The Rust Book (bahasa Inggris): bab resmi tentang cara mendefinisikan trait, method default, parameter
impl Trait, dan trait bound. - Traits — Rust by Example (bahasa Inggris): contoh yang bisa dijalankan dengan trait, method default, dan implementasinya.
- Derive — Rust by Example (bahasa Inggris): trait standar mana saja yang bisa diimplementasikan otomatis oleh
#[derive(...)], lengkap dengan contoh. - Advanced Traits — The Rust Book (bahasa Inggris): langkah berikutnya, dengan associated type, supertrait, dan pola newtype.
- Traits — The Rust Reference (bahasa Inggris): aturan lengkap tentang definisi trait, kalau kamu ingin tahu detailnya.