Interface #
Di Rust, konsep yang setara dengan interface di bahasa pemrograman lain (seperti Java atau C#) adalah traits. Traits mendefinisikan serangkaian metode yang dapat diimplementasikan oleh tipe tertentu. Traits memungkinkan Anda untuk menentukan perilaku bersama yang dapat digunakan oleh berbagai tipe tanpa harus membuat hirarki kelas, seperti yang dilakukan dalam paradigma berbasis objek.
Pengertian Traits #
Trait adalah kumpulan metode yang bisa dimiliki oleh tipe tertentu. Ketika suatu tipe mengimplementasikan trait, tipe tersebut wajib menyediakan definisi untuk metode-metode yang ada dalam trait tersebut. Traits digunakan untuk menetapkan kontrak tentang apa yang bisa dilakukan oleh suatu tipe, tanpa mempermasalahkan bagaimana tipe tersebut mengimplementasikan kontrak tersebut.
Definisi Trait:
trait NamaTrait {
fn nama_metode(&self);
}
Contoh Sederhana:
trait Greet {
fn say_hello(&self);
}
struct Person {
name: String,
}
impl Greet for Person {
fn say_hello(&self) {
println!("Hello, my name is {}!", self.name);
}
}
fn main() {
let person = Person { name: String::from("Alice") };
person.say_hello();
}
Penjelasan:
Greet
adalah trait yang mendefinisikan metodesay_hello
.Person
adalah struktur yang mengimplementasikan traitGreet
.- Ketika kita memanggil
say_hello
pada instancePerson
, implementasi dari traitGreet
digunakan.
Traits dengan Default Method #
Rust memungkinkan Anda untuk memberikan implementasi default untuk metode di dalam trait. Ini berarti bahwa tipe yang mengimplementasikan trait tersebut tidak wajib untuk mendefinisikan ulang metode yang sudah memiliki default implementation, kecuali mereka ingin menggantinya.
Contoh Traits dengan Default Method:
trait Greet {
fn say_hello(&self) {
println!("Hello, world!");
}
}
struct Person;
impl Greet for Person {}
fn main() {
let person = Person;
person.say_hello(); // Menggunakan default method
}
Penjelasan:
say_hello
memiliki implementasi default yang mencetak “Hello, world!”.Person
mengimplementasikanGreet
tanpa mendefinisikan ulangsay_hello
, sehingga default method digunakan.
Menggabungkan Traits #
Rust memungkinkan Anda untuk mendefinisikan sebuah trait yang menggabungkan beberapa trait lainnya, yang disebut sebagai supertrait. Ini berguna ketika Anda ingin memastikan bahwa tipe yang mengimplementasikan trait juga mengimplementasikan trait lain yang terkait.
Contoh Penggabungan Traits:
trait Human {
fn speak(&self);
}
trait Greet: Human {
fn greet(&self) {
self.speak();
println!("Nice to meet you!");
}
}
struct Person;
impl Human for Person {
fn speak(&self) {
println!("Hello!");
}
}
impl Greet for Person {}
fn main() {
let person = Person;
person.greet();
}
Penjelasan:
Greet
adalah supertrait dariHuman
, yang berarti setiap tipe yang mengimplementasikanGreet
harus juga mengimplementasikanHuman
.Person
mengimplementasikanHuman
, sehingga dapat mengimplementasikanGreet
.
Trait Bounds pada Generics #
Rust memungkinkan Anda untuk menggunakan traits sebagai batasan (bounds) pada tipe generik. Ini memastikan bahwa tipe generik hanya dapat digunakan jika mereka mengimplementasikan trait tertentu.
Contoh Trait Bounds pada Generics:
trait Summarize {
fn summarize(&self) -> String;
}
fn notify<T: Summarize>(item: T) {
println!("Summary: {}", item.summarize());
}
struct NewsArticle {
headline: String,
content: String,
}
impl Summarize for NewsArticle {
fn summarize(&self) -> String {
format!("{}: {}", self.headline, self.content)
}
}
fn main() {
let article = NewsArticle {
headline: String::from("New Rust Release!"),
content: String::from("Rust 1.56 has been released."),
};
notify(article);
}
Penjelasan:
Summarize
adalah trait yang mendefinisikan metodesummarize
.- Fungsi generik
notify
hanya menerima tipe yang mengimplementasikanSummarize
. NewsArticle
mengimplementasikanSummarize
, sehingga dapat diteruskan kenotify
.
Implementasi Trait untuk Tipe yang Ada #
Rust memungkinkan Anda untuk mengimplementasikan trait untuk tipe yang sudah ada, termasuk tipe-tipe bawaan Rust dan tipe dari crate eksternal. Namun, Anda tidak bisa mengimplementasikan trait untuk tipe yang ada jika trait dan tipe tersebut keduanya tidak didefinisikan di dalam crate yang sama (ini disebut Orphan Rule).
Contoh Implementasi Trait untuk Tipe yang Ada:
trait Double {
fn double(&self) -> i32;
}
impl Double for i32 {
fn double(&self) -> i32 {
self * 2
}
}
fn main() {
let x = 5;
println!("Double of {} is {}", x, x.double());
}
Penjelasan:
Double
adalah trait yang menyediakan metodedouble
.- Trait ini diimplementasikan untuk tipe
i32
, sehingga semua nilaii32
memiliki metodedouble
.
Trait Objects (Objek Trait) #
Rust mendukung dynamic dispatch melalui penggunaan trait objects. Trait objects memungkinkan Anda untuk menampung berbagai tipe yang mengimplementasikan trait tertentu dalam satu variabel atau struktur. Trait objects berguna ketika Anda ingin menggunakan polimorfisme.
Sintaks Trait Objects:
fn example_function(object: &dyn TraitName) {
// Blok kode
}
Contoh Trait Objects:
trait Draw {
fn draw(&self);
}
struct Circle;
struct Square;
impl Draw for Circle {
fn draw(&self) {
println!("Drawing a circle");
}
}
impl Draw for Square {
fn draw(&self) {
println!("Drawing a square");
}
}
fn main() {
let shapes: Vec<&dyn Draw> = vec![&Circle, &Square];
for shape in shapes {
shape.draw();
}
}
Penjelasan:
Draw
adalah trait yang mendefinisikan metodedraw
.Circle
danSquare
adalah struktur yang mengimplementasikanDraw
.shapes
adalah vektor dari trait objects (&dyn Draw
), yang dapat menampung referensi ke berbagai tipe yang mengimplementasikanDraw
.- Polimorfisme tercapai karena
draw
dapat dipanggil pada elemenshapes
tanpa mengetahui tipe konkret dari elemen tersebut.
impl Trait
dan Return Types
#
Rust menyediakan cara singkat untuk menentukan bahwa fungsi mengembalikan tipe yang mengimplementasikan trait tertentu menggunakan impl Trait
. Ini sangat berguna ketika Anda ingin menyembunyikan tipe konkret dari nilai yang dikembalikan.
Contoh impl Trait
:
trait Summarize {
fn summarize(&self) -> String;
}
struct NewsArticle {
headline: String,
content: String,
}
impl Summarize for NewsArticle {
fn summarize(&self) -> String {
format!("{}: {}", self.headline, self.content)
}
}
fn returns_summarizable() -> impl Summarize {
NewsArticle {
headline: String::from("Breaking News!"),
content: String::from("Something important happened."),
}
}
fn main() {
let article = returns_summarizable();
println!("Summary: {}", article.summarize());
}
Penjelasan:
- Fungsi
returns_summarizable
mengembalikan nilai dengan tipe yang tidak diketahui oleh pemanggil, kecuali bahwa nilai tersebut mengimplementasikanSummarize
. impl Trait
menyederhanakan deklarasi tipe kembalian ketika tipe konkret tidak penting bagi pemanggil.
Blanket Implementations #
Blanket Implementations adalah implementasi trait untuk semua tipe yang memenuhi syarat tertentu. Ini sangat berguna untuk mendefinisikan perilaku umum bagi semua tipe yang mengimplementasikan suatu trait lain.
Contoh Blanket Implementations:
trait Printable {
fn print(&self);
}
impl<T: std::fmt::Debug> Printable for T {
fn print(&self) {
println!("{:?}", self);
}
}
fn main() {
let x = 42;
let y = "Hello";
x.print();
y.print();
}
Penjelasan:
Printable
adalah trait yang mendefinisikan metodeprint
.- Blanket implementation ini membuat semua tipe yang mengimplementasikan
Debug
secara otomatis mengimplementasikanPrintable
. - Nilai
x
dany
dapat dicetak menggunakan metodeprint
karena mereka mengimplementasikanDebug
.
Kesimpulan #
Traits di Rust adalah mekanisme yang kuat untuk mendefinisikan dan mengimplementasikan perilaku bersama di berbagai tipe. Dengan traits, Anda dapat menghindari beberapa kelemahan dari inheritance (pewarisan) tradisional yang ditemukan di bahasa lain, dan malah fokus pada komposisi dan penggunaan perilaku yang di-share.
Traits memungkinkan Rust untuk memberikan konsep polimorfisme dan abstraksi tanpa kehilangan keamanan tipe atau performa. Memahami traits dan cara penggunaannya akan membantu Anda menulis kode Rust yang lebih fleksibel, modular, dan dapat digunakan kembali.