Struct & Impl — Rust

Struct di Rust Struct adalah cara untuk mengelompokkan data terkait menjadi satu tipe custom — mirip class di bahasa lain, tapi tanpa inheritance…

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 selesai menutup nota dengan parameter self. Sesudah println!("{}", pesanan.selesai());, baris berikutnya memanggil pesanan.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

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

&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

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

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 ==.

Yang akan kamu pelajari