Topik ini membahas tiga bentuk struct pada Rust beserta cara melengkapi struct dengan metode. Struct adalah tipe bentukan yang mengelompokkan beberapa nilai yang saling berhubungan ke dalam satu nama, sehingga data tidak lagi dioper sebagai tuple atau kumpulan variabel lepas. Setelah membaca topik ini, struct bernama, tuple struct, dan unit struct dapat didefinisikan sendiri serta dilengkapi metode pada blok impl dengan penerima self, &self, maupun &mut self.
Tiga bentuk struct#
Struct bernama (named struct) adalah bentuk yang paling sering dipakai. Setiap field diberi nama dan tipe secara eksplisit, dan nilai field diakses dengan sintaks titik seperti catatan.suhu. Satu aturan yang membedakan Rust dari banyak bahasa lain adalah penanda mut berlaku untuk seluruh instance, bukan untuk satu field. Apabila satu field perlu diubah, variabel yang menampung struct tersebut harus dideklarasikan dengan let mut. Selama variabel itu dipinjam sebagai &mut, tidak ada peminjaman lain yang boleh hidup bersamaan.
Dua bentuk lainnya menutup kebutuhan yang tidak terjawab oleh struct bernama. Tuple struct menyimpan data tanpa nama field, sehingga nilainya diakses lewat indeks seperti posisi.0. Bentuk ini cocok untuk tipe pembungkus yang memuat satu makna, misalnya JarakKm(f64), karena pemberian nama pada tipe sudah cukup untuk membuat program lebih jelas. Unit struct tidak memuat data sama sekali dan ditulis tanpa kurung kurawal, hanya diakhiri titik koma. Bentuk ini dipakai sebagai penanda (marker) ketika sebuah tipe hanya perlu menjadi pemilik metode atau implementasi trait, yakni kumpulan perilaku yang dapat dilekatkan pada sebuah tipe, tanpa menyimpan keadaan apa pun.
Program berikut memperlihatkan ketiga bentuk tersebut sekaligus sintaks singkat pengisian field. Ketika nama variabel lokal kebetulan sama dengan nama field, nama field boleh ditulis sendirian tanpa bentuk suhu: suhu.
struct CatatanSuhu {
id_sensor: String,
suhu: f64,
kelembapan: u8,
}
struct Titik(f64, f64);
struct PenandaAudit;
fn main() {
let suhu = 36.5;
let kelembapan = 60;
let catatan = CatatanSuhu {
id_sensor: String::from("sensor-A"),
suhu,
kelembapan,
};
println!(
"Sensor {} mencatat {} derajat, kelembapan {}%",
catatan.id_sensor, catatan.suhu, catatan.kelembapan
);
let posisi = Titik(2.0, 3.5);
println!("Titik berada di ({}, {})", posisi.0, posisi.1);
let _penanda = PenandaAudit;
println!("PenandaAudit tidak menyimpan data apa pun");
}Sensor sensor-A mencatat 36.5 derajat, kelembapan 60%
Titik berada di (2, 3.5)
PenandaAudit tidak menyimpan data apa punKeluaran tersebut menunjukkan tiga mekanisme akses yang berbeda. Field struct bernama dibaca dengan catatan.id_sensor, field tuple struct dibaca dengan indeks posisi.0 dan posisi.1, sedangkan unit struct tidak memiliki apa pun untuk dibaca. Nilai suhu dan kelembapan pada instance berasal dari variabel lokal bernama sama yang dideklarasikan di awal main, dan pemakaian {} pada f64 menampilkan representasi paling ringkas, sehingga 2.0 tercetak sebagai 2.
Rust juga menyediakan sintaks pembaruan struct (struct update syntax) untuk membuat struct baru dari sebagian besar field struct lama. Field yang tidak disebut diambil dari struct sumber yang ditulis setelah ...
// tidak-dijalankan
let dasar = CatatanSuhu {
id_sensor: String::from("sensor-B"),
suhu: 35.9,
kelembapan: 55,
};
let koreksi = CatatanSuhu {
suhu: 36.1,
..dasar
};Fragmen di atas tidak dijalankan karena bergantung pada definisi CatatanSuhu pada program sebelumnya di halaman ini, bukan program utuh. Pada instance koreksi, hanya suhu yang diisi manual, sedangkan id_sensor dan kelembapan diambil dari dasar. Perlu diperhatikan bahwa field yang diambil dari struct sumber akan dipindahkan apabila tipe field tersebut tidak menerapkan Copy, misalnya String. Setelah pemindahan itu terjadi, struct sumber tidak dapat lagi diakses penuh.
Metode pada blok impl#
Metode adalah fungsi yang dideklarasikan di dalam blok impl dan menerima self dalam salah satu bentuknya sebagai parameter pertama. Berbeda dengan bahasa yang menyimpan metode di dalam definisi tipe, Rust memisahkan data di struct dari perilaku di impl, sehingga satu tipe boleh memiliki beberapa blok impl pada berkas yang berbeda. Metode dipanggil dengan sintaks titik, dan Rust secara otomatis menambahkan referensi atau dereferensi yang sesuai, sehingga kertas.luas() bekerja tanpa perlu menulis (&kertas).luas().
Tiga bentuk penerima menentukan hak akses metode. Penerima &self meminjam instance untuk dibaca, &mut self meminjam instance untuk diubah, dan self mengambil kepemilikan instance sehingga instance tersebut tidak dapat dipakai lagi oleh pemanggil. Metode juga dapat menerima parameter tambahan setelah self, termasuk parameter yang bertipe sama dengan tipe instance penerimanya.
#[derive(Debug)]
struct PersegiPanjang {
panjang: f64,
lebar: f64,
}
impl PersegiPanjang {
fn luas(&self) -> f64 {
self.panjang * self.lebar
}
fn bisa_memuat(&self, lain: &PersegiPanjang) -> bool {
self.panjang > lain.panjang && self.lebar > lain.lebar
}
fn ubah_skala(&mut self, faktor: f64) {
self.panjang *= faktor;
self.lebar *= faktor;
}
}
fn main() {
let mut kertas = PersegiPanjang { panjang: 30.0, lebar: 21.0 };
let kartu = PersegiPanjang { panjang: 9.0, lebar: 6.0 };
println!("Luas kertas: {}", kertas.luas());
println!("Kertas memuat kartu: {}", kertas.bisa_memuat(&kartu));
kertas.ubah_skala(0.5);
println!("Kertas setelah disusutkan: {:?}", kertas);
}Luas kertas: 630
Kertas memuat kartu: true
Kertas setelah disusutkan: PersegiPanjang { panjang: 15.0, lebar: 10.5 }Metode luas hanya membaca data sehingga &self sudah memadai. Metode bisa_memuat memperlihatkan parameter tambahan bertipe &PersegiPanjang, sesuatu yang umum ketika dua instance tipe yang sama perlu dibandingkan. Metode ubah_skala mengubah field, sehingga penerimanya adalah &mut self dan variabel kertas wajib dideklarasikan dengan let mut. Baris terakhir memakai {:?} yang memerlukan trait Debug pada tipe tersebut.
Kesalahan umum#
Kesalahan pertama yang sering terjadi adalah memanggil metode &mut self pada variabel yang tidak dideklarasikan mut. Kompilator menolak pemanggilan itu dengan error E0596 karena peminjaman yang bisa mengubah nilai hanya sah pada variabel mutabel. Contoh berikut menampilkan error aslinya.
export PATH="$HOME/.cargo/bin:$PATH"
# buat program yang lupa mendeklarasikan mut
printf 'struct Penghitung {\n nilai: i32,\n}\n\nimpl Penghitung {\n fn naik(&mut self) {\n self.nilai += 1;\n }\n}\n\nfn main() {\n let p = Penghitung { nilai: 0 };\n p.naik();\n println!("{}", p.nilai);\n}\n' > lupa-mut.rs
# kompilasi untuk melihat error E0596
rustc --edition=2021 lupa-mut.rs 2>&1 | sed -n '1,10p'error[E0596]: cannot borrow `p` as mutable, as it is not declared as mutable
--> lupa-mut.rs:13:5
|
13 | p.naik();
| ^ cannot borrow as mutable
|
help: consider changing this to be mutable
|
12 | let mut p = Penghitung { nilai: 0 };
| +++Perbaikannya tercantum langsung pada saran kompilator, yaitu mengubah deklarasi menjadi let mut p. Kesalahan kedua adalah menginisialisasi struct dengan field yang tidak lengkap. Kompilator menolaknya dengan error E0063 yang menyebutkan field mana yang kurang, misalnya pesan missing field `kelembapan` in initializer of `CatatanSuhu` beserta lokasi initializer yang bermasalah. Tidak seperti bahasa yang mengisi field kosong dengan nilai bawaan, Rust selalu meminta seluruh field diisi secara eksplisit pada saat pembuatan instance, sehingga data yang belum diinisialisasi tidak pernah terbaca dan tidak ada nilai bawaan yang bersembunyi di balik sintaks. Kebiasaan membaca pesan error sampai selesai, termasuk bagian help dan note, mempersingkat penelusuran karena hampir semua saran perbaikannya sudah menyebutkan bentuk kode yang dimaksud.
Ringkasan#
- Struct bernama mengelompokkan field yang diberi nama, tuple struct membungkus data tanpa nama field, dan unit struct menjadi penanda tanpa data.
- Penanda
mutberlaku untuk seluruh instance, bukan untuk satu field. - Metode dideklarasikan pada blok
impl. Penerima&selfmeminjam untuk dibaca,&mut selfmeminjam untuk diubah, danselfmengambil kepemilikan. - Sintaks singkat field dan struct update syntax mempersingkat pembuatan instance, dengan konsekuensi pemindahan kepemilikan pada field yang tidak
Copy.
Referensi#
- Defining and Instantiating Structs — The Rust Programming Language — Definisi struct bernama, tuple struct, unit struct, sintaks singkat field, dan struct update syntax.
- Method Syntax — The Rust Programming Language — Blok impl, berbagai bentuk penerima self, dan pemanggilan metode.