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:
Greetadalah trait yang mendefinisikan metodesay_hello.Personadalah struktur yang mengimplementasikan traitGreet.- Ketika kita memanggil
say_hellopada instancePerson, implementasi dari traitGreetdigunakan.
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_hellomemiliki implementasi default yang mencetak “Hello, world!”.PersonmengimplementasikanGreettanpa 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:
Greetadalah supertrait dariHuman, yang berarti setiap tipe yang mengimplementasikanGreetharus juga mengimplementasikanHuman.PersonmengimplementasikanHuman, 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:
Summarizeadalah trait yang mendefinisikan metodesummarize.- Fungsi generik
notifyhanya menerima tipe yang mengimplementasikanSummarize. NewsArticlemengimplementasikanSummarize, 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:
Doubleadalah trait yang menyediakan metodedouble.- Trait ini diimplementasikan untuk tipe
i32, sehingga semua nilaii32memiliki 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:
Drawadalah trait yang mendefinisikan metodedraw.CircledanSquareadalah struktur yang mengimplementasikanDraw.shapesadalah vektor dari trait objects (&dyn Draw), yang dapat menampung referensi ke berbagai tipe yang mengimplementasikanDraw.- Polimorfisme tercapai karena
drawdapat dipanggil pada elemenshapestanpa 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_summarizablemengembalikan nilai dengan tipe yang tidak diketahui oleh pemanggil, kecuali bahwa nilai tersebut mengimplementasikanSummarize. impl Traitmenyederhanakan 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:
Printableadalah trait yang mendefinisikan metodeprint.- Blanket implementation ini membuat semua tipe yang mengimplementasikan
Debugsecara otomatis mengimplementasikanPrintable. - Nilai
xdanydapat dicetak menggunakan metodeprintkarena 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.