Topik ini membahas fungsi terasosiasi (associated function), yaitu fungsi yang dideklarasikan di dalam blok impl tanpa parameter self, sehingga dipanggil langsung pada tipenya dan bukan pada sebuah instance. Pembahasan mencakup pola constructor new, peran kata kunci Self, dan pembuatan beberapa constructor untuk satu tipe. Setelah membaca topik ini, instance dapat dibuat melalui fungsi buatan sendiri sehingga pemanggil tidak perlu mengisi field satu per satu, dan perbedaan pemanggilan dengan titik serta dengan titik dua ganda tidak lagi tertukar.
Fungsi terasosiasi dan sintaks titik dua ganda#
Fungsi terasosiasi berbeda dari metode pada satu hal penting: fungsi ini tidak memiliki self, sehingga tidak ada instance yang menjadi penerimanya. Fungsi jenis ini dipanggil dengan sintaks titik dua ganda, seperti PenghitungKata::new(...), dan bukan dengan titik seperti pemanggilan metode. Sintaks :: sebenarnya sudah sering dipakai pada tipe pustaka standar, misalnya String::from dan Vec::new, yang keduanya adalah fungsi terasosiasi milik tipenya masing-masing.
Pola yang paling umum adalah constructor bernama new yang bertugas merakit instance dari bahan yang lebih sederhana. Manfaat utamanya adalah perancangan tipe menjadi lebih rapi karena aturan pembentukan nilai berada di satu tempat, bukan tersebar pada setiap pemanggil. Berikut contoh tipe penghitung kata yang dibangun dari sepotong teks.
struct PenghitungKata {
teks: String,
}
impl PenghitungKata {
fn new(teks: &str) -> Self {
Self {
teks: String::from(teks),
}
}
fn jumlah_kata(&self) -> usize {
self.teks.split_whitespace().count()
}
fn jumlah_karakter(&self) -> usize {
self.teks.chars().count()
}
}
fn main() {
let catatan = PenghitungKata::new("catatan harian piket kebersihan ruang server");
let kosong = PenghitungKata::new("");
println!("Jumlah kata: {}", catatan.jumlah_kata());
println!("Jumlah karakter: {}", catatan.jumlah_karakter());
println!("Teks kosong berisi {} kata", kosong.jumlah_kata());
}Jumlah kata: 6
Jumlah karakter: 44
Teks kosong berisi 0 kataFungsi new menerima &str, kemudian mengubahnya menjadi String milik instance sendiri. Pemanggil tidak perlu tahu bahwa field internal bertipe String, sehingga representasi data dapat diubah tanpa mengubah semua pemanggil. Pada main, pembuatan instance hanya memerlukan satu baris PenghitungKata::new(...), sedangkan pemanggilan jumlah_kata dan jumlah_karakter tetap menggunakan titik karena keduanya adalah metode dengan penerima &self. Pola ini juga dipakai pustaka standar, misalnya Vec::new yang membuat vector kosong tanpa argumen.
Tanda :: sebenarnya berfungsi lebih luas sebagai pemisah jalur (path), bukan khusus pemanggilan fungsi terasosiasi. Penulisan std::f64::consts::PI menempuh jalur dari crate std, yaitu unit kompilasi terbesar pada Rust, melalui modul f64 dan consts, sampai ke konstanta PI. Pemanggilan PenghitungKata::new(...) juga merupakan jalur, yaitu menuju nama new yang dimiliki tipe PenghitungKata. Metode tidak dipanggil lewat jalur seperti itu karena penerimanya adalah sebuah instance, bukan tipenya, sehingga pemanggilannya memakai sintaks titik. Sebagai patokan ringkas, apabila self tidak ada di daftar parameter, pemanggilan wajib memakai ::, dan apabila self ada, pemanggilan memakai titik pada sebuah instance.
Kata kunci Self dan beberapa constructor#
Dalam blok impl, kata kunci Self adalah alias untuk tipe yang tertulis setelah impl. Penulisan nilai kembalian sebagai Self alih-alih nama tipe membuat isi blok tetap benar ketika nama tipe diganti, karena hanya baris impl yang perlu disunting. Kata kunci ini juga berperan saat satu tipe memerlukan lebih dari satu constructor karena satu nama new tidak cukup untuk semua kebutuhan. Tipe berikut memiliki tiga fungsi terasosiasi, yaitu new, nol, dan dari_meter.
#[derive(Debug)]
struct JarakKm(f64);
impl JarakKm {
fn new(km: f64) -> Self {
Self(km)
}
fn nol() -> Self {
Self(0.0)
}
fn dari_meter(meter: f64) -> Self {
Self(meter / 1000.0)
}
fn nilai(&self) -> f64 {
self.0
}
}
fn main() {
let a = JarakKm::new(21.1);
let b = JarakKm::nol();
let c = JarakKm::dari_meter(500.0);
println!("a = {:?}", a);
println!("b = {:?}", b);
println!("c = {:?}", c);
println!("Total jarak: {}", a.nilai() + b.nilai() + c.nilai());
}a = JarakKm(21.1)
b = JarakKm(0.0)
c = JarakKm(0.5)
Total jarak: 21.6Setiap fungsi terasosiasi mengembalikan Self sehingga ketiganya menghasilkan nilai bertipe JarakKm yang sama, tetapi bahan bakunya berbeda. Pemakaian {:?} pada tuple struct yang memiliki derive Debug menampilkan nama tipe beserta isinya, termasuk bentuk JarakKm(0.0) untuk nilai nol. Metode nilai tetap dipanggil dengan titik karena parameternya &self, dan penulisan self.0 pada metode itu mengakses satu-satunya field tuple struct tanpa nama. Konvensi penamaan constructor selain new juga lazim pada pustaka standar, misalnya Vec::with_capacity yang membuat vector dengan kapasitas awal tertentu.
Keberadaan nol dan dari_meter memperlihatkan alasan pemakaian fungsi terasosiasi melebihi kenyamanan penulisan. Pemanggil dapat menulis JarakKm(0.0) secara langsung karena tuple struct mengizinkannya, tetapi JarakKm::nol() menyatakan maksud dengan jelas tanpa memaksa pembaca memeriksa satuan yang dipakai di dalam tipe. Demikian pula dari_meter(500.0) menyembunyikan konversi pembagian seribu sehingga pemanggil tidak perlu mengulang rumus itu di banyak tempat, dan tidak ada salinan rumus yang dapat keliru. Semakin banyak aturan pembentukan nilai yang berada di dalam impl, semakin kecil peluang program menyimpan nilai yang tidak konsisten. Nama fungsi terasosiasi mengikuti konvensi penamaan Rust, yaitu huruf kecil dengan pemisah garis bawah, sama seperti penamaan fungsi biasa.
Kesalahan umum#
Kesalahan yang paling sering terjadi adalah memanggil fungsi terasosiasi dengan sintaks titik seolah-olah fungsi itu metode. Kompilator menolaknya dengan error E0599 dan secara eksplisit menyebut bahwa yang dimaksud adalah associated function, bukan metode. Contoh berikut menampilkan error aslinya.
export PATH="$HOME/.cargo/bin:$PATH"
# buat program yang memanggil fungsi terasosiasi dengan titik
printf 'struct Titik {\n x: i32,\n y: i32,\n}\n\nimpl Titik {\n fn new(x: i32, y: i32) -> Self {\n Self { x, y }\n }\n}\n\nfn main() {\n let a = Titik::new(1, 2);\n let b = a.new(3, 4);\n println!("{} {}", b.x, b.y);\n}\n' > salah-panggil.rs
# kompilasi untuk melihat error E0599
rustc --edition=2021 salah-panggil.rs 2>&1 | sed -n '1,19p'error[E0599]: no method named `new` found for struct `Titik` in the current scope
--> salah-panggil.rs:14:15
|
1 | struct Titik {
| ------------ method `new` not found for this struct
...
14 | let b = a.new(3, 4);
| ^^^ this is an associated function, not a method
|
= note: found the following associated functions; to be used as methods, functions must have a `self` parameter
note: the candidate is defined in an impl for the type `Titik`
--> salah-panggil.rs:7:5
|
7 | fn new(x: i32, y: i32) -> Self {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: use associated function syntax instead
|
14 - let b = a.new(3, 4);
14 + let b = Titik::new(3, 4);Saran perbaikannya tertera pada bagian help, yaitu mengganti a.new(3, 4) dengan Titik::new(3, 4). Bagian note pada pesan itu juga menunjukkan lokasi kandidat yang ditemukan di dalam blok impl, sehingga penelusuran tetap singkat meski pesannya panjang. Kesalahan kedua yang berhubungan adalah tertukarnya self dan Self. Penulisan Self sebagai parameter pertama metode, maupun pemakaian self sebagai nilai kembalian constructor, ditolak sebagai error sintaks karena masing-masing kata kunci menempati posisi yang tidak seharusnya. Cara mengingatnya sederhana: self mengisi daftar parameter, sedangkan Self mengisi posisi tipe seperti nilai kembalian dan anotasi parameter.
Kesalahan ketiga adalah lupa tanda kurung pada pemanggilan, misalnya let t: Titik = Titik::new;. Bentuk seperti itu menyimpan fungsi itu sendiri sebagai nilai alih-alih hasil pemanggilannya, sehingga kompilator menolaknya dengan error E0308 yang menyatakan expected Titik, found fn item. Tanpa anotasi tipe, bentuk ini justru lolos kompilasi karena fungsi memang sah menjadi nilai, sehingga kesalahan baru muncul pada pemakaian variabelnya di bawah. Pemanggilan yang benar selalu diakhiri tanda kurung berisi argumen, yaitu Titik::new(1, 2), dan pesan errornya menunjuk langsung ke definisi fungsi terasosiasi yang bersangkutan.
Ringkasan#
- Fungsi terasosiasi dideklarasikan di blok
impltanpaselfdan dipanggil dengan sintaksTipe::nama_fungsi. - Constructor
newadalah konvensi pembuat instance, sementara constructor lain sepertidari_metermenyediakan bahan baku yang berbeda. Selfadalah alias tipe pada blokimpl, sedangkanselfadalah parameter penerima metode.- Pemanggilan dengan titik hanya untuk metode yang memiliki
self. Pemanggilan fungsi terasosiasi lewat titik ditolak dengan error E0599.
Referensi#
- Method Syntax — The Rust Programming Language — Bagian associated functions, kata kunci Self, dan konvensi constructor new.
- Struct Vec — Standard library
— Dokumentasi Vec yang memperlihatkan fungsi terasosiasi
newdanwith_capacity.