Struct mengelompokkan beberapa nilai yang saling terkait jadi satu tipe baru buatanmu sendiri, dan impl menempelkan fungsi ke tipe itu. Bayangin lembar nota pesanan di Kedai Martabak Bang Rusdi. Formatnya sudah tercetak: kolom nama pelanggan, daftar harga item, dan kotak centang "dibungkus". Tiap pembeli dapat satu lembar dengan isi sendiri. Kasir juga punya prosedur tetap untuk nota: menambah item, menghitung total, dan menutup nota saat pembeli bayar. Format lembar itu struct, satu lembar yang sudah diisi itu satu nilai struct, dan prosedurnya impl.
🧪 Tebakan cepat: method
selesaimenutup nota dengan parameterself. Sesudahprintln!("{}", pesanan.selesai());, baris berikutnya memanggilpesanan.total(). Lolos compile? Tebak dulu, jawabannya ada di bagian "&self, &mut self, dan self".
Mendefinisikan struct dan mengisinya
struct Pesanan {
pelanggan: String,
item: Vec<u32>,
dibungkus: bool,
}
fn main() {
let mut pesanan = Pesanan {
pelanggan: String::from("Sari"),
item: vec![32000, 32000, 38000],
dibungkus: false,
};
pesanan.dibungkus = true;
println!("{} pesan {} item", pesanan.pelanggan, pesanan.item.len());
println!("Dibungkus: {}", pesanan.dibungkus);
}
Sari pesan 3 item
Dibungkus: true
struct Pesanan { ... }menulis formatnya: tiap field punya nama dan tipe. Nama struct ditulis huruf besar di depan,Pesanan.Pesanan { pelanggan: ..., item: ..., dibungkus: ... }mengisi satu lembar. Semua field wajib diisi, urutannya bebas.pesanan.pelangganmembaca satu field dengan titik. Mengubah field butuh seluruh variabelnyalet mut: Rust gak punyamutper field.
impl: method dan associated function
struct Pesanan {
pelanggan: String,
item: Vec<u32>,
dibungkus: bool,
}
impl Pesanan {
fn baru(pelanggan: String) -> Pesanan {
Pesanan { pelanggan, item: Vec::new(), dibungkus: false }
}
fn tambah(&mut self, harga: u32) {
self.item.push(harga);
}
fn total(&self) -> u32 {
self.item.iter().sum()
}
fn selesai(self) -> String {
let cara = if self.dibungkus { "dibungkus" } else { "makan di sini" };
format!("{} bayar {}, {}", self.pelanggan, self.total(), cara)
}
}
fn main() {
let mut pesanan = Pesanan::baru(String::from("Sari"));
pesanan.tambah(32000);
pesanan.tambah(32000);
pesanan.tambah(38000);
pesanan.dibungkus = true;
println!("Total: {}", pesanan.total());
println!("{}", pesanan.selesai());
}
Total: 102000
Sari bayar 102000, dibungkus
- Semua fungsi di dalam
impl Pesanan { ... }milik tipePesanan. barugak punya parameterself: ini associated function, dipanggil lewat nama tipenya,Pesanan::baru(...), sama sepertiString::fromdanVec::new. Fungsi pembuat seperti ini lazim dinamainew; di sini kita pakaibaru.Pesanan { pelanggan, ... }adalah field init shorthand: kalau nama variabel sama dengan nama field, cukup tulis sekali. Artinya sama denganpelanggan: pelanggan.tambah,total, danselesaipunyaselfsebagai parameter pertama: ini method, dipanggil dengan titik,pesanan.total(). Di dalamnya,selfadalah nota yang sedang dipakai, jadiself.itemadalah daftar item nota itu.
&self, &mut self, dan self
Bentuk self menentukan apa yang terjadi pada nota saat method dipanggil:
| Parameter pertama | Artinya | Contoh | Sesudah dipanggil |
|---|---|---|---|
&self |
meminjam untuk dibaca | total |
pesanan masih bisa dipakai |
&mut self |
meminjam untuk diubah | tambah |
masih bisa dipakai; variabelnya wajib let mut |
self |
mengambil notanya (move) | selesai |
pesanan sudah pindah, gak bisa dipakai lagi |
Ini kode tebakan tadi, pesanan.total() sesudah selesai():
error[E0382]: borrow of moved value: `pesanan`
--> src/main.rs:34:20
|
27 | let mut pesanan = Pesanan::baru(String::from("Sari"));
| ----------- move occurs because `pesanan` has type `Pesanan`, which does not implement the `Copy` trait
...
33 | println!("{}", pesanan.selesai());
| --------- `pesanan` moved due to this method call
34 | println!("{}", pesanan.total());
| ^^^^^^^ value borrowed here after move
|
note: `Pesanan::selesai` takes ownership of the receiver `self`, which moves `pesanan`
Gak lolos. Itu jawaban tebakannya. selesai(self) mengambil notanya, persis seperti String yang pindah ke dapur di pelajaran pertama, jadi sesudahnya pesanan kosong. Pakai self cuma untuk method yang memang mengakhiri nilainya. Kalau let mut lupa ditulis, tiap tambah ditolak dengan cannot borrow `pesanan` as mutable, as it is not declared as mutable (E0596).
#[derive(Debug, Clone, PartialEq)] dan {:#?}
Struct buatanmu awalnya gak bisa dicetak dengan {:?}, disalin dengan .clone(), atau dibandingkan dengan ==. Baris #[derive(...)] di atas struct menyuruh compiler menulis kemampuan itu otomatis:
#[derive(Debug, Clone, PartialEq)]
struct Pesanan {
pelanggan: String,
item: Vec<u32>,
dibungkus: bool,
}
// impl Pesanan { ... } sama seperti di atas
fn main() {
let mut pesanan = Pesanan::baru(String::from("Sari"));
pesanan.tambah(32000);
pesanan.tambah(32000);
pesanan.tambah(38000);
let salinan = pesanan.clone();
println!("{}", pesanan == salinan);
println!("{:?}", pesanan);
println!("{:#?}", pesanan);
println!("{}", pesanan.selesai());
}
true
Pesanan { pelanggan: "Sari", item: [32000, 32000, 38000], dibungkus: false }
Pesanan {
pelanggan: "Sari",
item: [
32000,
32000,
38000,
],
dibungkus: false,
}
Sari bayar 102000, makan di sini
Debugmembuat{:?}(satu baris) dan{:#?}(rapi, satu field per baris) bisa dipakai. Teks dicetak dengan kutip.Clonememberi.clone(): salinan lengkap, termasuk Vec-nya.PartialEqmemberi==: dua nota sama kalau semua field-nya sama.
Update syntax, tuple struct, dan unit struct
..sari mengambil field sisanya dari nota lain. Tapi field yang bukan angka dipindah, bukan disalin:
let budi = Pesanan {
pelanggan: String::from("Budi"),
dibungkus: true,
..sari
};
println!("{:?}", sari);
Baris terakhir ditolak: borrow of partially moved value: `sari` , karena sari.item (Vec) sudah pindah ke budi. Kalau sari masih dipakai, tulis ..sari.clone().
Tuple struct punya field tanpa nama, dibaca dengan nomor: struct Rupiah(u32);, lalu let total = Rupiah(102000); dan total.0 memberi 102000. Gunanya membuat tipe baru yang gak bisa tertukar: fn bayar(jumlah: Rupiah) menolak bayar(5000) dengan expected `Rupiah`, found integer. Unit struct gak punya field sama sekali, struct Kasir;, dan nilainya ditulis Kasir. Ia berguna kalau yang penting cuma tipenya, seperti struct Kucing; di latihan pelajaran traits.
⚠️ Jebakan yang sering ketemu
- Lupa
let mut. Method&mut selfdan mengubah field ditolak (E0596). - Memakai nilai sesudah method
self.selesai(self)memindahkan notanya:borrow of moved value. - Lupa
#[derive(Debug)].println!("{:?}", pesanan)ditolak denganthe trait `Debug` is not implemented for `Pesanan`, danhelp:-nya menyarankan#[derive(Debug)]. - Ada field yang gak diisi.
Pesanan { pelanggan: ..., item: ... }tanpadibungkusditolak:missing field `dibungkus` in initializer of `Pesanan`. ..sarilalu memakaisari. Field String dan Vec ikut pindah. Pakai..sari.clone().
TL;DR: struct Pesanan { pelanggan: String, item: Vec<u32>, dibungkus: bool } membuat tipe baru dengan field bernama, diisi dengan Pesanan { ... } dan dibaca dengan pesanan.item. impl Pesanan menempelkan fungsi: associated function tanpa self dipanggil Pesanan::baru(...), method dengan self dipanggil pesanan.total(). &self meminjam untuk dibaca, &mut self meminjam untuk diubah (variabelnya let mut), self mengambil nilainya sehingga gak bisa dipakai lagi. Field init shorthand menulis pelanggan sekali, ..sari mengambil sisa field (dan memindahkannya). Tuple struct Rupiah(u32) dibaca dengan .0, unit struct Kasir gak punya field. #[derive(Debug, Clone, PartialEq)] memberi {:?}/{:#?}, .clone(), dan ==.