Smart Pointers, Macros, dan Advanced Patterns di Rust • Erlkim

Smart Pointers, Macros, dan Advanced Patterns di Rust

Smart Pointers, Macros, dan Advanced Patterns di Rust
Jun 13, 2026 ... views
15 min

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. 1.Tipe dengan ukuran tidak diketahui di compile time
  2. 2.Data besar yang ingin dipindahkan tanpa copy
  3. 3.Trait objects (dynamic dispatch)
  4. 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

PointerOwnershipThread-safeMutability
BoxSingleYaMutable
RcMultipleTidakImmutable
ArcMultipleYaImmutable
RefCellSingleTidakInterior mut
WeakNoneTidakImmutable

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

KebutuhanPilihan
Data di heap, single ownerBox
Multiple owner, single-threadedRc
Multiple owner, multi-threadedArc
Interior mutability, single-threadedRefCell
Interior mutability, multi-threadedMutex
Prevent reference cyclesWeak

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

0/2000