Halo teman-teman semuanya, pada artikel sebelumnya kita sudah membahas tentang generics di Rust. Kita melihat bagaimana generics membuat fungsi, struct, dan enum menjadi lebih fleksibel serta bisa digunakan untuk berbagai tipe data tanpa perlu menulis kode berulang.
Kita juga mengenal trait bound sebagai cara membatasi tipe generik agar hanya bisa menggunakan tipe yang memiliki perilaku tertentu.
Nah, kali ini kita akan membahas lebih dalam tentang trait itu sendiri. Trait adalah fitur penting di Rust yang memungkinkan kita mendefinisikan perilaku (behavior) yang bisa dibagi ke berbagai tipe. Jika di bahasa lain seperti Java dikenal dengan interface, atau di Go dikenal dengan interface juga, maka di Rust konsep serupa ini disebut trait.
Dengan trait, kita bisa mendefinisikan kumpulan method yang harus diimplementasikan oleh suatu tipe. Trait juga menjadi dasar dari banyak fitur Rust, mulai dari polymorphism, trait bound, hingga operator overloading. Mari kita bahas satu per satu.
Membuat Trait
Kita bisa membuat trait dengan keyword trait
, lalu mendefinisikan method-method di dalamnya.
trait Deskripsi {
fn deskripsi(&self) -> String;
}
Trait Deskripsi
di atas mendefinisikan sebuah method deskripsi
yang harus mengembalikan String
.
Mengimplementasikan Trait
Untuk membuat sebuah struct atau tipe mengimplementasikan trait, kita gunakan keyword impl
.
struct Person {
name: String,
age: u32,
}
trait Deskripsi {
fn deskripsi(&self) -> String;
}
impl Deskripsi for Person {
fn deskripsi(&self) -> String {
format!("{} berusia {}", self.name, self.age)
}
}
fn main() {
let user = Person {
name: String::from("Andi"),
age: 25,
};
println!("{}", user.deskripsi());
}
Di sini struct Person
mengimplementasikan trait Deskripsi
, sehingga kita bisa memanggil user.deskripsi()
.
Default Implementation
Trait juga bisa memberikan implementasi default pada method-nya.
trait Greet {
fn say_hello(&self) {
println!("Hello dari trait!");
}
}
struct Robot;
impl Greet for Robot {}
fn main() {
let r = Robot;
r.say_hello(); // menggunakan default implementation
}
Kita bisa menimpa implementasi default jika diperlukan.
Trait Bound pada Generics
Seperti yang sudah kita lihat sebelumnya, trait bisa digunakan untuk membatasi generics.
use std::fmt::Display;
fn cetak<T: Display>(x: T) {
println!("Isi: {}", x);
}
fn main() {
cetak(42);
cetak("Rustacean");
}
Di sini hanya tipe yang mengimplementasikan trait Display
yang bisa digunakan.
Multiple Trait Bound
Kita juga bisa memberikan lebih dari satu trait bound.
use std::fmt::{Display, Debug};
fn cetak_info<T: Display + Debug>(x: T) {
println!("Display: {}", x);
println!("Debug: {:?}", x);
}
fn main() {
let angka = 100;
cetak_info(angka);
}
Operator Overloading dengan Trait
Di Rust, operator seperti +
atau *
diimplementasikan melalui trait. Misalnya, trait std::ops::Add
digunakan untuk operator penjumlahan.
use std::ops::Add;
#[derive(Debug)]
struct Point {
x: i32,
y: i32,
}
impl Add for Point {
type Output = Point;
fn add(self, other: Point) -> Point {
Point {
x: self.x + other.x,
y: self.y + other.y,
}
}
}
fn main() {
let p1 = Point { x: 1, y: 2 };
let p2 = Point { x: 3, y: 4 };
let hasil = p1 + p2;
println!("{:?}", hasil);
}
Dengan mengimplementasikan trait Add
, kita bisa menggunakan operator +
pada struct Point
.
Kesimpulan
Pada artikel ini kita sudah mempelajari tentang trait di Rust. Kita melihat bagaimana mendefinisikan trait, mengimplementasikan trait pada struct, menggunakan default implementation, trait bound pada generics, multiple trait bound, hingga operator overloading.
Trait adalah salah satu pilar utama Rust, karena dengan trait kita bisa mendefinisikan perilaku yang konsisten, reusable, dan aman di berbagai tipe.
Pada artikel berikutnya, kita akan membahas tentang Module & Package, yaitu bagaimana cara mengatur kode Rust agar lebih terstruktur menggunakan mod
, use
, dan crate
.
Terima Kasih