Struct mengelompokkan data, tetapi perilaku data tersebut masih tersebar sebagai fungsi-fungsi bebas di berbagai sudut file. Fungsi subtotal menerima &OrderItem, fungsi format_label menerima &OrderItem juga, dan keduanya tidak punya ikatan formal dengan tipe yang mereka layani — pembaca kode harus menebak-nebak hubungan itu sendiri. Rust menyatukan data dan perilaku lewat blok impl: semua fungsi yang melayani sebuah tipe dideklarasikan di satu tempat, dan fungsi yang menerima self disebut method — dipanggil dengan sintaks titik yang sudah akrab dari pemanggilan teks.len() atau teks.push_str(...) pada String. Pada chapter ini kita akan memindahkan perilaku item pesanan ke dalam blok impl dan memahami tiga bentuk penerimaan self.
Dari Fungsi Bebas ke Metode #
Mari kita mulai dari keadaan lama: struct OrderItem plus fungsi bebas yang membacanya.
struct OrderItem {
name: String,
price: u64,
quantity: u64,
}
fn subtotal(item: &OrderItem) -> u64 {
item.price * item.quantity
}
fn main() {
let item = OrderItem {
name: String::from("Kopi Gayo"),
price: 25000,
quantity: 3,
};
println!("{} subtotal Rp{}", item.name, subtotal(&item));
}Output yang Dihasilkan:
Kopi Gayo subtotal Rp75000Kode ini sah dan berjalan, tetapi perhatikan dua hal. Pertama, hubungan antara OrderItem dan subtotal hanya hidup di kepala pembaca — tidak ada yang memaksa subtotal diletakkan dekat struct-nya. Kedua, pemanggilan subtotal(&item) menuntut kita mengingat nama fungsi lalu menyuplik pinjaman &item secara manual. Semakin banyak operasi untuk satu tipe, semakin banyak beban ingatan ini. Blok impl menyelesaikan keduanya sekaligus.
Menulis Blok impl #
Kata kunci impl diikuti nama tipe membuka blok yang menjadi rumah resmi bagi seluruh method tipe tersebut. Mari kita pindahkan perilaku OrderItem:
struct OrderItem {
name: String,
price: u64,
quantity: u64,
}
impl OrderItem {
fn subtotal(&self) -> u64 {
self.price * self.quantity
}
fn label(&self) -> String {
format!("{} x{} (Rp{})", self.name, self.quantity, self.subtotal())
}
fn apply_discount(&mut self, percent: u64) {
self.price = self.price * (100 - percent) / 100;
}
}
fn main() {
let mut item = OrderItem {
name: String::from("Kopi Gayo"),
price: 25000,
quantity: 3,
};
println!("Sebelum diskon: {}", item.label());
item.apply_discount(20);
println!("Setelah diskon: {}", item.label());
println!("Subtotal akhir: Rp{}", item.subtotal());
}Output yang Dihasilkan:
Sebelum diskon: Kopi Gayo x3 (Rp75000)
Setelah diskon: Kopi Gayo x3 (Rp60000)
Subtotal akhir: Rp60000Mari kita bedah kode di atas baris per baris:
- Baris 7 (
impl OrderItem {): blokimpldibuka dengan nama tipe yang dilayani. Semuafndi dalamnya secara otomatis terikat padaOrderItem. - Baris 8 (
fn subtotal(&self) -> u64): parameter pertama&selfadalah ciri method.selfadalah nama khusus untuk instance yang memanggil, dan&di depannya berarti method ini hanya meminjam — isi baris 9 mengalikanself.pricedenganself.quantitysehingga25000 * 3menghasilkan75000tanpa mengambil alih kepemilikan item. Penulisan&selfadalah singkatan dariself: &OrderItem. - Baris 12 (
fn label(&self) -> String): method kedua juga meminjam, lalu membentuk teks baru denganformat!di baris 13. Perhatikan pemanggilanself.subtotal()di baris yang sama — method boleh memanggil method lain pada instance yang sama melaluiself. - Baris 16 (
fn apply_discount(&mut self, percent: u64)): tanda&mut selfmenyatakan method ini mengubah isi struct. Rumus di baris 17 memangkas harga 20 persen:25000 * 80 / 100menjadi20000. - Baris 22 (
let mut item = ...): variabel harusmutkarena akan diubah olehapply_discount— mutabilitas variabel dan mutabilitas penerimaselfharus sejalan. - Baris 30 (
item.apply_discount(20);): pemanggilan method memakai operator titik —item.apply_discount(20), bukanapply_discount(&mut item, 20). Kompilator yang menyuplik referensi&mutsecara otomatis. - Baris 32-33 (
item.label()danitem.subtotal()): dua method baca dipanggil berurutan setelah perubahan. Subtotal akhir20000 * 3adalah60000, sesuai output.
Mekanisme Pemanggilan Method di Balik Layar #
Sintaks titik menyembunyikan satu langkah kerja kompilator bernama automatic referencing and dereferencing (referensi dan dereferensi otomatis). Saat kita menulis item.apply_discount(20), kompilator menimbang tipe penerima: method meminta &mut OrderItem, sedangkan item adalah OrderItem, maka kompilator menyusun (&mut item).apply_discount(20) secara diam-diam. Proses yang sama bekerja untuk &self — item.subtotal() diterjemahkan menjadi (&item).subtotal(). Berkat mekanisme ini kita tidak perlu menulis & atau &mut pada setiap pemanggilan method; pencocokan dilakukan berdasarkan signature method yang dituju.
Tiga Bentuk self: Pinjam, Pinjam Ubah, atau Miliki #
Ketiga bentuk penerima di bawah ini adalah seluruh pilihan yang tersedia, dan aturannya persis aturan borrowing dari Section sebelumnya:
&self— method pembaca. Instance tetap hidup dan bisa dipakai lagi. Cocok untuk kalkulasi dan format sepertisubtotaldanlabel.&mut self— method pengubah. Hanya boleh dipanggil pada variabelmut, dan selama pemanggilan berlangsung tidak ada peminjam lain yang aktif. Cocok untuk mutasi di tempat sepertiapply_discount.self— method pemilik. Instance dikonsumsi; setelah pemanggilan selesai, pemanggil kehilangan nilai tersebut. Cocok untuk transformasi yang mengubah nilai menjadi sesuatu yang lain, misalnya method yang mengemasOrderItemmenjadi struk final lalu item aslinya memang tidak dibutuhkan lagi.
Ketiga bentuk tersebut juga berlaku untuk enum — blok impl tidak eksklusif milik struct. Pemilihan bentuk mengikuti kebutuhan method terhadap data: semakin sedikit akses yang dibutuhkan, semakin banyak kebebasan yang diberikan kepada pemanggil. Method &self bisa dipanggil pada instance apa pun, method self hanya pada instance yang sah untuk dimakan.
Sebagai gambaran bentuk ketiga, bayangkan OrderItem dilengkapi method fn into_receipt(self) -> String yang membentuk struk final dari nama, harga, dan jumlah item. Tanda tangan self tanpa & menyatakan bahwa pemanggil menyerahkan item-nya untuk selamanya — struk tercetak, item lenyap, dan pemanggil yang mencoba memanggil item.label() setelahnya akan berjumpa E0382 persis seperti kasus struct yang dipindahkan ke fungsi pada chapter pertama Section ini. Method pemilik jarang dibutuhkan pada data yang dipakai berulang, tetapi tepat untuk data yang memang sekali pakai pada satu alur transformasi.
Ringkasan #
Blok impl menjadikan struct sebagai rumah resmi bagi perilakunya: method dideklarasikan dengan fn nama(&self, ...) dan dipanggil lewat operator titik, sementara kompilator menyuplik & atau &mut secara otomatis dari signature method. Tiga bentuk self — pinjam, pinjam ubah, dan miliki — mengikuti aturan borrowing yang telah kita kuasai tanpa pengecualian. Method yang kita tulis sejauh ini selalu terikat pada instance, padahal String::from dan Vec::new dipanggil tanpa instance sama sekali. Bentuk fungsi impl semacam itu akan kita bahas pada Chapter Associated Functions.