Struktur dan Implementasi#
Struktur dan Implementasi#
Sebuah struct
atau structure digunakan untuk merepresentasikan tipe data kompleks yang kita definisikan sendiri. Pada Rust, kita dapat membuat sebuah tipe baru dengan menggunakan struct
. Kita dapat menambahkan method pada struct dengan menggunakan implementation block atau impl
.
Struct#
struct
merupakan sebuah cara untuk mendefinisikan sebuah tipe data yang kompleks. Sebuah struct
dapat memiliki beberapa field, yang masing-masing dapat memiliki tipe data yang berbeda. Sebagai contoh, kita akan membuat sebuah struct
yang merepresentasikan seseorang. Kita akan menggunakan struct
untuk menyimpan data-data yang berkaitan dengan orang tersebut.
struct Person {
name: String,
age: u8,
}
Lalu kita dapat membuat sebuah variabel baru bertipe Person
dengan cara berikut:
fn main() {
let name = String::from("Fulan");
let age = 27;
let person = Person { name, age };
}
Derive Trait#
Derive Trait adalah sebuah cara untuk mendapatkan implementasi dari beberapa trait secara otomatis. Sebagai contoh, kita dapat menggunakan #[derive(Debug)]
untuk mendapatkan implementasi dari trait Debug
secara otomatis. Contoh penggunaan derive trait adalah sebagai berikut:
#[derive(Debug)]
struct Person {
name: String,
age: u8,
}
Sekarang, kita akan membahas beberapa derive trait berguna yang paling sering dipakai pada bahasa Rust.
Sebagai catatan, untuk memakai derive trait, seluruh field dalam struct
harus juga telah mengimplementasikan trait
yang kita derive tersebut. Karena itu, kita tidak akan bisa menggunakan trait
Copy
tanpa Clone
bila ada String
didalam field struct
kita.
Debug#
Debug
merupakan trait yang sangat berguna untuk melakukan debugging. Ia akan mencetak output sesuai dengan bentuk asli dari tipe kita. Pada macro print!
atau println!
, untuk dapat mencetak Debug
, kita harus menggunakan formatting berikut: :?
.
Contoh penggunaan:
#[derive(Debug)]
struct Person {
name: String,
age: u8,
}
fn main() {
let name = String::from("Fulan");
let age = 27;
let person = Person { name, age };
println!("{:?}", person);
}
// OUTPUT: Person { name: "Fulan", age: 27 }
Clone#
Masih ingat dengan materi ownership sebelumnya? Dengan mengimplementasikan Clone
, kita dapat melakukan cloning pada tipe kita dengan method clone()
seperti yang sudah pernah dijelaskan di materi ownership sebelumnya.
Contoh penggunaan:
#[derive(Clone, Debug)]
struct Person {
name: String,
age: u8,
}
fn main() {
let name = String::from("Fulan");
let age = 27;
let person = Person { name, age };
let person2 = person.clone();
println!("{:?}", person2);
}
Copy#
Seperti Clone
, Copy
juga merupakan trait yang berguna untuk melakukan cloning. Namun, Copy
memiliki beberapa syarat yang harus dipenuhi agar dapat digunakan. Syarat-syarat tersebut adalah sebagai berikut:
Semua field dalam
struct
harus mengimplementasikanCopy
(Contohnya, kita tidak akan bisa menggunakanCopy
untuk tipe yang memiliki field bertipeString
didalamnya.).
struct
tidak boleh memiliki implementasi dariDrop
trait.
Dengan Copy
, tipe kita akan melakukan copy secara otomatis ketika kita melakukan assignment, seperti yang telah dijelaskan tentang trait Copy
pada artikel ownership sebelumnya. Saat men-derive Copy
, kita juga harus men-derive Clone
Contoh penggunaan:
#[derive(Copy, Clone, Debug)]
struct Location {
lat: f64,
lon: f64,
}
fn main() {
let loc = Location { lat: 10.55555, lon: 20.22222 };
let loc2 = loc; // Copy
let loc3 = loc2; // Copy
println!("{:?}", loc);
println!("{:?}", loc2);
println!("{:?}", loc3);
}
Membuat format print untuk tipe kita#
Kita dapat membuat format tertentu dan bagaimana kita akan mencetak tipe kita sendiri dengan menggunakan trait Display
. Untuk Display
, kita tidak dapat menggunakan derive trait. Display
akan mengimplementasikan to_string()
secara otomatis juga pada tipe kita.
Pneggunaan Display
adalah sebagai berikut:
use std::fmt;
#[Clone, Debug]
struct Person {
name: String,
age: u8,
}
impl fmt::Display for Person {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Seseorang bernama {} dan berumur {}", self.name, self.age)
}
}
fn main() {
let name = String::from("Fulan");
let age = 27;
let person = Person { name, age };
println!("{}", person); // Seseorang bernama Fulan dan berumur 27
}
println!
akan mencetak sesuai dengan yang telah kita definisikan pada implementasi Display kita, yang berada didalam macro write!
.
Membuat method untuk tipe kita#
Kita dapat membuat method untuk tipe kita sendiri dengan menggunakan impl
block. Contoh penggunaan adalah sebagai berikut:
#[derive(Clone, Debug)]
struct Person {
name: String,
age: u8,
}
impl Person {
fn new(name: String, age: u8) -> Person {
Person { name, age }
}
fn say_hello(&self) {
println!("Halo, nama saya {} dan berumur {}", self.name, self.age);
}
fn birthday(&mut self) {
self.age += 1;
}
}
fn main() {
let name = String::from("Fulan");
let age = 27;
let person = Person::new(name, age);
person.say_hello();
println!("Saya ulang tahun!");
person.birthday();
println!("Umur saya sekarang adalah {}", person.age);
}
Pada kode diatas, kita memiliki dua tipe method, yaitu method yang dapat langsung dipanggil dari tipenya, dan method yang harus dipanggil melalui variabel yang menyimpan tipe tersebut. Method yang dapat langsung dipanggil dari tipenya disebut dengan method associated function
. Method yang harus dipanggil melalui variabel yang menyimpan tipe tersebut disebut dengan method instance method
. Method associated function biasanya digunakan untuk membuat instance dari tipe tersebut, seperti yang terlihat pada method new() pada kode diatas. Ia dipanggil dengan menggunakan ::
setelah nama tipe, seperti yang terlihat pada Person::new(name, age)
. Namun, method instance method biasanya digunakan untuk melakukan operasi pada instance dari tipe tersebut, seperti yang terlihat pada method say_hello()
dan birthday()
pada kode diatas. Ia dipanggil dengan menggunakan .
setelah variabel yang menyimpan tipe tersebut, seperti yang terlihat pada person.say_hello()
dan person.birthday()
.
// TODO: Tambahkan iterator, operator overloading, etc.
# LATIHAN
// TODO