Lewati ke konten

Struct di Rust

Sebuah struct (struktur) mengelompokkan beberapa nilai yang saling berhubungan ke dalam satu tipe yang punya nama. Kalau tuple adalah bungkusan cepat tanpa nama, struct memberi nama pada setiap field-nya, sehingga kode yang memakainya jauh lebih mudah dibaca.

struct Person {
name: String,
age: u32,
}
fn main() {
let alice = Person {
name: String::from("Alice"),
age: 30,
};
println!("Umur {} {} tahun", alice.name, alice.age);
}

Output:

Umur Alice 30 tahun
▶ Coba sendiri

Bandingkan dengan tuple ("Alice", 30): dengan struct, alice.name dan alice.age sudah menjelaskan dirinya sendiri. Kamu tidak perlu mengingat bahwa field 0 adalah nama dan field 1 adalah umur.

Sama seperti variabel, instance struct secara default bersifat immutable (tidak dapat diubah). Tambahkan mut kalau kamu ingin mengubah sebuah field setelah instance-nya dibuat:

struct Person {
name: String,
age: u32,
}
fn main() {
let mut alice = Person {
name: String::from("Alice"),
age: 30,
};
alice.age += 1;
println!("Sekarang umur {} {} tahun", alice.name, alice.age);
}
▶ Coba sendiri

Kamu bisa menempelkan fungsi ke sebuah struct dengan blok impl. Di dalamnya, &self merujuk ke instance tempat method itu dipanggil:

struct Person {
name: String,
age: u32,
}
impl Person {
fn greet(&self) {
println!("Halo, aku {} dan umurku {} tahun", self.name, self.age);
}
}
fn main() {
let alice = Person { name: String::from("Alice"), age: 30 };
alice.greet();
}

Output:

Halo, aku Alice dan umurku 30 tahun
▶ Coba sendiri

Pola yang umum adalah membuat fungsi new di dalam impl yang membangun instance untukmu, supaya pemanggilnya tidak perlu menuliskan setiap field satu per satu:

struct Person {
name: String,
age: u32,
}
impl Person {
fn new(name: &str, age: u32) -> Person {
Person { name: name.to_string(), age }
}
}
fn main() {
let bob = Person::new("Bob", 25);
println!("Umur {} {} tahun", bob.name, bob.age);
}
▶ Coba sendiri

Perhatikan age yang berdiri sendiri di dalam Person { name: name.to_string(), age }. Kalau sebuah variabel punya nama yang persis sama dengan field-nya, kamu tidak perlu menulis age: age, cukup age saja. Rust menyebut ini field init shorthand (singkatan inisialisasi field).

Coba jalankan println!("{:?}", alice) pada struct seperti yang ditulis di atas, dan kodenya tidak akan bisa di-compile. Rust tidak tahu cara mencetak tipe buatanmu dalam format debug kecuali kamu memintanya. Tambahkan #[derive(Debug)] di atas struct untuk mendapatkan kemampuan itu secara gratis:

#[derive(Debug)]
struct Person {
name: String,
age: u32,
}
fn main() {
let alice = Person { name: String::from("Alice"), age: 30 };
println!("{:?}", alice);
}

Output:

Person { name: "Alice", age: 30 }
▶ Coba sendiri

#[derive(...)] adalah cara Rust membuat fungsionalitas umum untuk sebuah tipe secara otomatis. Kamu akan sering sekali melihat Debug, dan nanti Clone serta yang lainnya, ditambahkan dengan cara ini.

Buat struct Rectangle dengan field width dan height (u32). Beri dua method: area, yang mengembalikan luasnya, dan is_square, yang mengembalikan true kalau kedua sisinya sama panjang. Petunjuk: kedua method hanya membaca field, jadi keduanya menerima &self.

Lihat solusi
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
fn is_square(&self) -> bool {
self.width == self.height
}
}
fn main() {
let wide = Rectangle { width: 8, height: 3 };
let square = Rectangle { width: 5, height: 5 };
println!("lebar: luas {}, persegi? {}", wide.area(), wide.is_square());
println!("kotak: luas {}, persegi? {}", square.area(), square.is_square());
}

Output:

lebar: luas 24, persegi? false
kotak: luas 25, persegi? true
▶ Coba sendiri

Buat struct Account dengan owner (String) dan balance dalam satuan sen (u32). Tulis Account::new("Ana") yang memulai saldo dari 0. Lalu setor 50 dan 25 sen dengan mengubah field-nya, dan cetak rekening itu dengan dua cara: dengan kalimat biasa dan dengan {:?}. Petunjuk: variabelnya harus let mut sebelum kamu bisa mengubah account.balance, dan {:?} butuh #[derive(Debug)].

Lihat solusi
#[derive(Debug)]
struct Account {
owner: String,
balance: u32,
}
impl Account {
fn new(owner: &str) -> Account {
Account { owner: owner.to_string(), balance: 0 }
}
}
fn main() {
let mut account = Account::new("Ana");
account.balance += 50;
account.balance += 25;
println!("{} punya {} sen", account.owner, account.balance);
println!("{:?}", account);
}

Output:

Ana punya 75 sen
Account { owner: "Ana", balance: 75 }
▶ Coba sendiri

new sendiri yang mengisi balance: 0, jadi pemanggilnya tidak mungkin lupa. Itulah salah satu alasan besar kenapa fungsi constructor sangat umum dipakai.

Masukkan tiga nilai Person ke dalam sebuah vektor: Ana (34), Ben (16), dan Chen (52). Cetak nama semua orang yang berumur 18 tahun ke atas, lalu cetak siapa yang paling tua. Petunjuk: lakukan perulangan dengan for person in &people, dan simpan orang tertua sejauh ini dengan let mut oldest = &people[0];.

Lihat solusi
struct Person {
name: String,
age: u32,
}
fn main() {
let people = vec![
Person { name: String::from("Ana"), age: 34 },
Person { name: String::from("Ben"), age: 16 },
Person { name: String::from("Chen"), age: 52 },
];
let mut oldest = &people[0];
for person in &people {
if person.age >= 18 {
println!("{} sudah dewasa", person.name);
}
if person.age > oldest.age {
oldest = person;
}
}
println!("Paling tua: {} ({})", oldest.name, oldest.age);
}

Output:

Ana sudah dewasa
Chen sudah dewasa
Paling tua: Chen (52)
▶ Coba sendiri

oldest adalah referensi (&Person) ke dalam vektor, bukan salinan, jadi tidak ada yang di-clone. Setiap kali kita menemukan orang yang lebih tua, kita cukup mengarahkan oldest ke orang itu.

  • Struct mengelompokkan field-field bernama ke dalam satu tipe: struct Person { name: String, age: u32 }.
  • Buat instance dengan Person { name: ..., age: ... }; tandai dengan mut kalau nanti ingin mengubah field-nya.
  • Tambahkan perilaku dengan blok impl; &self merujuk ke instance tempat method dipanggil.
  • Tambahkan #[derive(Debug)] supaya struct bisa dicetak dengan {:?}.

Kuis singkat

1. Apa kelebihan utama struct dibandingkan tuple?

2. Apa yang bisa kamu lakukan setelah menambahkan #[derive(Debug)] ke sebuah struct?

3. Pada Person { name: name.to_string(), age }, apa fungsi age yang berdiri sendiri?

Skor: 0 / 3