Smart Pointers, Macros, dan Advanced Patterns di Rust
Di artikel sebelumnya kita sudah belajar collections, iterators, dan closures. Sekarang masuk ke level advanced: smart pointers, macros, dan advanced patterns.
Smart Pointers
Smart pointers adalah tipe data yang bertindak seperti pointer tapi punya metadata tambahan. Di Rust, smart pointers memiliki data yang mereka point to dan bisa mengelola memori otomatis.
Box
Box<T> adalah smart pointer paling sederhana. Box mengalokasikan data di heap dan mengembalikan pointer.
fn main() {
let b = Box::new(5);
println!("b = {}", b);
}
Kapan menggunakan Box:
- 1.Tipe dengan ukuran tidak diketahui di compile time
- 2.Data besar yang ingin dipindahkan tanpa copy
- 3.Trait objects (dynamic dispatch)
- 4.Rekursif data structure
Contoh Linked List dengan Box:
#[derive(Debug)]
enum List {
Cons(i32, Box<List>),
Nil,
}
fn main() {
let list = List::Cons(1,
Box::new(List::Cons(2,
Box::new(List::Cons(3,
Box::new(List::Nil))))));
println!("{:?}", list);
}
Tanpa Box, compiler tidak bisa menentukan ukuran enum List karena rekursi tanpa batas.
Box untuk Trait Objects:
trait Animal {
fn speak(&self) -> String;
}
struct Dog;
struct Cat;
impl Animal for Dog {
fn speak(&self) -> String { String::from("Woof!") }
}
impl Animal for Cat {
fn speak(&self) -> String { String::from("Meow!") }
}
fn main() {
let animals: Vec<Box<dyn Animal>> = vec![
Box::new(Dog),
Box::new(Cat),
Box::new(Dog),
];
for animal in &animals {
println!("{}", animal.speak());
}
}
Rc
Rc<T> (Reference Counting) memungkinkan multiple ownership. Data dihapus saat reference terakhir di-drop.
rust
rustuse std::rc::Rc;
fn main() {
let a = Rc::new(String::from("hello"));
println!("Count: {}", Rc::strong_count(&a)); // 1
let b = Rc::clone(&a);
println!("Count: {}", Rc::strong_count(&a)); // 2
let c = Rc::clone(&a);
println!("Count: {}", Rc::strong_count(&a)); // 3
drop(c);
println!("Count: {}", Rc::strong_count(&a)); // 2
}
Gunakan Rc saat data dimiliki oleh multiple part dan hanya dibaca, tidak dimodifikasi. Single-threaded saja, gunakan Arc untuk multi-threaded.
RefCell
RefCell<T> memungkinkan interior mutability. Kamu bisa memodifikasi data meskipun reference-nya immutable. Borrow checking dipindahkan dari compile time ke runtime.
rust
rustuse std::cell::RefCell;
fn main() {
let data = RefCell::new(vec![1, 2, 3]);
data.borrow_mut().push(4);
println!("{:?}", data.borrow()); // [1, 2, 3, 4]
}
Kombinasi Rc dan RefCell memungkinkan multiple ownership dengan mutable data:
rust
rustuse std::cell::RefCell;
use std::rc::Rc;
fn main() {
let shared = Rc::new(RefCell::new(vec![1, 2, 3]));
let owner1 = Rc::clone(&shared);
let owner2 = Rc::clone(&shared);
owner1.borrow_mut().push(4);
owner2.borrow_mut().push(5);
println!("{:?}", shared.borrow()); // [1, 2, 3, 4, 5]
}
Weak
Weak<T> adalah reference yang tidak mencegah data di-drop. Berguna untuk mencegah reference cycles:
rust
rustuse std::rc::{Rc, Weak};
fn main() {
let strong = Rc::new(String::from("hello"));
let weak = Rc::downgrade(&strong);
println!("Strong: {:?}", strong);
println!("Weak: {:?}", weak.upgrade()); // Some("hello")
drop(strong);
println!("Weak after drop: {:?}", weak.upgrade()); // None
}
Smart Pointers Summary
| Pointer | Ownership | Thread-safe | Mutability |
|---|---|---|---|
| Box | Single | Ya | Mutable |
| Rc | Multiple | Tidak | Immutable |
| Arc | Multiple | Ya | Immutable |
| RefCell | Single | Tidak | Interior mut |
| Weak | None | Tidak | Immutable |
Macros
Macro adalah kode yang menulis kode lain. Macros diekspansi di compile time. Ada dua jenis: declarative macros (macro_rules!) dan procedural macros.
Declarative Macros
rust
rustmacro_rules! hashmap {
($( $key:expr => $value:expr ),* $(,)?) => {
{
let mut map = std::collections::HashMap::new();
$( map.insert($key, $value); )*
map
}
};
}
fn main() {
let scores = hashmap! {
"Alice" => 95,
"Bob" => 80,
"Charlie" => 90,
};
println!("{:?}", scores);
}
Syntax: $key:expr menangkap expression, $( ... ),* repetisi dipisahkan koma.
Macro untuk Mengurangi Duplikasi
rust
rustmacro_rules! make_struct {
($name:ident { $($field:ident : $type:ty),* $(,)? }) => {
#[derive(Debug)]
struct $name {
$(pub $field: $type),*
}
impl $name {
fn new($($field: $type),*) -> Self {
$name { $($field),* }
}
}
};
}
make_struct!(User { name: String, email: String, age: u32 });
make_struct!(Product { title: String, price: f64, stock: u32 });
fn main() {
let user = User::new(
String::from("ERLKIM"),
String::from("erlkim@mail.com"),
25,
);
let product = Product::new(String::from("Laptop"), 999.99, 50);
println!("{:?}", user);
println!("{:?}", product);
}
Built-in Macros
Rust punya banyak macro bawaan:
rust
rustfn main() {
// vec! - membuat Vec
let v = vec![1, 2, 3];
// println! dan format!
println!("Hello, {}!", "Rust");
let s = format!("Hello, {}!", "Rust");
// dbg! - debug print dengan file dan line
let x = 5;
let y = dbg!(x * 2);
// stringify! - konversi token ke string
let expr = stringify!(1 + 2);
// concat!
let greeting = concat!("Hello", ", ", "World!");
// cfg!
if cfg!(target_os = "linux") {
println!("Running on Linux");
}
println!("v: {:?}, y: {}, s: {}", v, y, s);
}
Macro lain yang berguna: todo!(), unimplemented!(), panic!(), include_str!(), include_bytes!(), env!().
Advanced Patterns
Newtype Pattern
Membungkus tipe yang sudah ada dalam struct baru untuk type safety:
rust
ruststruct Meters(f64);
struct Kilometers(f64);
impl Meters {
fn to_kilometers(&self) -> Kilometers {
Kilometers(self.0 / 1000.0)
}
}
fn calculate_distance(distance: Kilometers) -> f64 {
distance.0 * 0.621371
}
fn main() {
let distance = Meters(5000.0);
println!("{} = {} km", distance.0, distance.to_kilometers().0);
let km = Kilometers(10.0);
println!("{} km = {} miles", km.0, calculate_distance(km));
}
Tanpa newtype, semua f64 terlihat sama. Dengan newtype, compiler mencegah kesalahan tipe.
Type Alias
rust
rusttype Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
type Matrix = Vec<Vec<f64>>;
fn process(data: Matrix) -> Result<()> {
for row in &data {
println!("{:?}", row);
}
Ok(())
}
fn main() {
let matrix = vec![
vec![1.0, 2.0, 3.0],
vec![4.0, 5.0, 6.0],
];
match process(matrix) {
Ok(()) => println!("Success"),
Err(e) => println!("Error: {}", e),
}
}
Drop Trait
Drop trait mendefinisikan kode yang dijalankan saat value keluar dari scope:
rust
ruststruct DatabaseConnection {
url: String,
}
impl DatabaseConnection {
fn new(url: &str) -> Self {
println!("Connecting to: {}", url);
DatabaseConnection { url: url.to_string() }
}
fn query(&self, sql: &str) {
println!("Executing on {}: {}", self.url, sql);
}
}
impl Drop for DatabaseConnection {
fn drop(&mut self) {
println!("Disconnecting from: {}", self.url);
}
}
fn main() {
let conn = DatabaseConnection::new("postgres://localhost/mydb");
conn.query("SELECT * FROM users");
println!("Doing other work...");
// conn di-drop otomatis di sini
}
Deref Trait
Deref memungkinkan smart pointer digunakan seperti reference biasa:
rust
rustuse std::ops::Deref;
struct MyBox<T>(T);
impl<T> MyBox<T> {
fn new(x: T) -> MyBox<T> { MyBox(x) }
}
impl<T> Deref for MyBox<T> {
type Target = T;
fn deref(&self) -> &T { &self.0 }
}
fn hello(name: &str) {
println!("Hello, {}!", name);
}
fn main() {
let m = MyBox::new(String::from("Rust"));
hello(&m); // Deref coercion: MyBox<String> -> &String -> &str
}
Typestate Pattern
Pattern yang memastikan operasi hanya dilakukan dalam urutan yang benar:
rust
rustuse std::marker::PhantomData;
struct Locked;
struct Unlocked;
struct Door<State> {
_state: PhantomData<State>,
}
impl Door<Locked> {
fn new() -> Self { Door { _state: PhantomData } }
fn unlock(self) -> Door<Unlocked> {
println!("Door unlocked");
Door { _state: PhantomData }
}
}
impl Door<Unlocked> {
fn open(&self) { println!("Door opened"); }
fn lock(self) -> Door<Locked> {
println!("Door locked");
Door { _state: PhantomData }
}
}
fn main() {
let door = Door::<Locked>::new();
let door = door.unlock();
door.open();
let door = door.lock();
// door.open(); // ERROR! Door is locked
let door = door.unlock();
door.open(); // OK!
}
Compiler memastikan kamu tidak bisa membuka pintu yang terkunci!
Kapan Menggunakan Setiap Smart Pointer
| Kebutuhan | Pilihan |
|---|---|
| Data di heap, single owner | Box |
| Multiple owner, single-threaded | Rc |
| Multiple owner, multi-threaded | Arc |
| Interior mutability, single-threaded | RefCell |
| Interior mutability, multi-threaded | Mutex |
| Prevent reference cycles | Weak |
Kesimpulan
Smart pointers, macros, dan advanced patterns membawa kode Rust ke level berikutnya:
- Box untuk heap allocation dan trait objects
- Rc untuk shared ownership
- RefCell untuk interior mutability
- Macros untuk metaprogramming dan menghilangkan duplikasi
- Advanced patterns seperti newtype, typestate, dan visitor
Ini adalah artikel terakhir dalam seri Rust. Dengan enam artikel yang sudah dibahas, kamu sudah punya fondasi yang sangat kuat untuk menjadi Rust developer. Terus praktik, baca dokumentasi resmi, dan kontribusi ke open source Rust projects.
~Erlkim
Komentar