Smart Pointer di Rust
Aturan ownership (kepemilikan) dan borrowing (peminjaman) sudah mencakup sebagian besar situasi. Tapi ada beberapa kasus nyata yang tidak pas dengan aturan “tepat satu pemilik”: nilai yang ukurannya baru diketahui saat program berjalan, data yang memang butuh lebih dari satu pemilik, atau nilai yang perlu diubah lewat referensi bersama. Smart pointer (pointer pintar) adalah jawaban Rust untuk masing-masing kasus itu.
Box<T>: menaruh nilai di heap
Section titled “Box<T>: menaruh nilai di heap”Secara bawaan, nilai disimpan di stack, dan compiler perlu tahu ukurannya sejak awal. Box<T> memindahkan nilai ke heap dan memberimu pointer berukuran tetap yang menunjuk ke sana. Ini berguna saat ukuran sebuah tipe tidak bisa diketahui saat kompilasi, paling sering pada tipe rekursif:
fn main() { let boxed = Box::new(5); println!("{}", *boxed); // * untuk mengambil nilainya}Output:
5Contoh klasik saat kamu butuh Box: tipe yang berisi dirinya sendiri, seperti node pada linked list:
enum List { Cons(i32, Box<List>), Nil,}
fn main() { let list = List::Cons(1, Box::new(List::Cons(2, Box::new(List::Nil))));
match list { List::Cons(value, _) => println!("Nilai pertama: {value}"), List::Nil => println!("Kosong"), }}Output:
Nilai pertama: 1Tanpa Box, Cons(i32, List) akan butuh ruang tak terhingga untuk disimpan: sebuah List yang berisi List yang berisi List, terus tanpa henti. Box<List> adalah pointer berukuran tetap, jadi compiler bisa menghitung berapa banyak ruang yang dibutuhkan.
Rc<T>: banyak pemilik
Section titled “Rc<T>: banyak pemilik”Aturan ownership bilang sebuah nilai punya tepat satu pemilik. Hampir selalu itu memang tepat, tapi kadang kamu benar-benar butuh beberapa bagian programmu berbagi data yang sama. Rc<T> (“reference counted”, dihitung referensinya) memungkinkan hal itu, dengan mencatat berapa banyak pemilik yang ada saat ini:
use std::rc::Rc;
fn main() { let a = Rc::new(String::from("data bersama")); let b = Rc::clone(&a); // bukan salinan penuh, hanya pemilik tambahan let c = Rc::clone(&a);
println!("{a} {b} {c}"); println!("Jumlah pemilik: {}", Rc::strong_count(&a));}Output:
data bersama data bersama data bersamaJumlah pemilik: 3Rc::clone tidak menyalin data di dalamnya. Ia hanya menaikkan sebuah penghitung dan memberikan referensi lain ke nilai yang sama. Datanya baru dibersihkan setelah semua pemiliknya keluar dari scope.
RefCell<T>: mengubah nilai lewat referensi bersama
Section titled “RefCell<T>: mengubah nilai lewat referensi bersama”Biasanya, aturan borrowing diperiksa saat kompilasi: boleh banyak referensi hanya-baca, atau satu referensi mutable, tapi tidak boleh keduanya sekaligus. RefCell<T> memindahkan pemeriksaan yang sama itu ke saat program berjalan (runtime), sehingga kamu bisa mengubah nilai bahkan lewat sesuatu yang terlihat seperti referensi bersama tanpa mut:
use std::cell::RefCell;
fn main() { let value = RefCell::new(5);
*value.borrow_mut() += 1;
println!("{}", value.borrow());}Output:
6RefCell paling sering dipasangkan dengan Rc. Rc<RefCell<T>> memberimu nilai dengan banyak pemilik yang tetap bisa diubah, sesuatu yang tidak bisa dilakukan Rc<T> sendirian.
Kapan sebaiknya memakai ini
Section titled “Kapan sebaiknya memakai ini”Jangan langsung mulai dengan smart pointer. Mulailah dengan ownership dan borrowing biasa, dan pakai smart pointer hanya saat compiler jelas-jelas bilang caramu tidak muat: tipe rekursif (Box), nilai yang memang butuh banyak pemilik (Rc), atau perubahan lewat referensi bersama yang tidak diizinkan aturan borrowing (RefCell).
Latihan
Section titled “Latihan”1. Menjumlahkan linked list
Section titled “1. Menjumlahkan linked list”Pakai enum List dari bagian Box. Buat list 1, 2, 3 dan tulis fungsi fn sum(list: &List) -> i32 yang menjumlahkan semua nilainya. Petunjuk: lakukan match pada list-nya. Untuk Cons(value, rest), jawabannya adalah value ditambah jumlah dari rest. Untuk Nil, jawabannya 0.
Lihat solusi
enum List { Cons(i32, Box<List>), Nil,}
fn sum(list: &List) -> i32 { match list { List::Cons(value, rest) => value + sum(rest), List::Nil => 0, }}
fn main() { let list = List::Cons(1, Box::new(List::Cons(2, Box::new(List::Cons(3, Box::new(List::Nil)))))); println!("Jumlah: {}", sum(&list));}Output:
Jumlah: 6Fungsi ini memanggil dirinya sendiri untuk sisa list sampai mencapai Nil. Perhatikan bahwa kamu bisa memberikan rest (sebuah &Box<List>) di tempat yang mengharapkan &List. Rust otomatis “melihat ke dalam” Box untukmu.
2. Daftar belanja bersama
Section titled “2. Daftar belanja bersama”Buat satu daftar belanja (sebuah Vec<String>) yang dimiliki oleh dua orang, alice dan bob, dan keduanya bisa menambahkan barang ke daftar itu. Masing-masing menambahkan satu barang. Lalu cetak daftarnya dan jumlah pemiliknya. Petunjuk: bungkus Vec-nya dengan Rc<RefCell<...>>, beri setiap orang sebuah Rc::clone, lalu panggil .borrow_mut().push(...) untuk menambah barang.
Lihat solusi
use std::cell::RefCell;use std::rc::Rc;
fn main() { let list = Rc::new(RefCell::new(Vec::new())); let alice = Rc::clone(&list); let bob = Rc::clone(&list);
alice.borrow_mut().push(String::from("susu")); bob.borrow_mut().push(String::from("roti"));
println!("{:?}", list.borrow()); println!("Jumlah pemilik: {}", Rc::strong_count(&list));}Output:
["susu", "roti"]Jumlah pemilik: 3Ada tiga pemilik: list, alice, dan bob. Ketiganya menunjuk ke Vec yang sama, jadi barang yang ditambahkan lewat alice juga terlihat lewat bob dan list. Tidak ada variabel yang butuh mut, karena RefCell yang mengurus perubahannya.
Ringkasan
Section titled “Ringkasan”Box<T>menyimpan nilai di heap dengan pointer berukuran tetap. Ini dibutuhkan untuk tipe rekursif.Rc<T>memungkinkan banyak pemilik untuk nilai yang sama di kode single-thread, lewat reference counting; pakaiArc<T>untuk antar-thread.RefCell<T>memeriksa aturan borrowing saat program berjalan, bukan saat kompilasi, sehingga nilai bisa diubah lewat referensi bersama. Tapi program akan panic kalau aturannya dilanggar.- Pakai smart pointer hanya saat ownership dan borrowing biasa tidak cocok untuk situasimu.
Kuis singkat
Section titled “Kuis singkat”Kuis singkat
1. Kenapa tipe rekursif seperti node linked list butuh Box<T>?
2. Apa yang sebenarnya dilakukan Rc::clone(&a)?
3. Kapan RefCell<T> memeriksa aturan borrowing?
4. Apa yang diberikan Rc<RefCell<T>> yang tidak bisa diberikan Rc<T> sendirian?
Skor: 0 / 4
Bacaan lebih lanjut
Section titled “Bacaan lebih lanjut”- Using
Box<T>to Point to Data on the Heap — The Rust Book (bahasa Inggris): cerita lengkap tentangBox, termasuk contoh cons list yang dipakai di halaman ini. Rc<T>, the Reference Counted Smart Pointer — The Rust Book (bahasa Inggris): kepemilikan bersama langkah demi langkah, sambil melihat penghitungnya naik dan turun.RefCell<T>and the Interior Mutability Pattern — The Rust Book (bahasa Inggris): kapan dan kenapa pemeriksaan borrowing dipindahkan ke saat program berjalan, dengan contoh yang realistis.std::cell— standard library docs (bahasa Inggris): penjelasan tentangCelldanRefCell, dan kapan memilih masing-masing.- Rc — Rust by Example (bahasa Inggris): contoh singkat yang bisa dijalankan, mencetak jumlah referensi saat pemilik datang dan pergi.