Box
Box
adalah type yang cukup membantu di Rust. Jika kita menggunakan Box
, kita bisa memasukkan type ke dalam heap, alih-alih menempatkannya pada stack. Untuk membuat Box
, cukup tuliskan Box::new()
dan letakkan item di dalamnya.
fn just_takes_a_variable<T>(item: T) {} // Ambil parameter dari type apapun dan lakukan drop. fn main() { let my_number = 1; // Ini adalah i32 just_takes_a_variable(my_number); just_takes_a_variable(my_number); // Tidak ada masalah menggunakan function ini dua kali, karena my_number adalah Copy let my_box = Box::new(1); // Ini adalah Box<i32> just_takes_a_variable(my_box.clone()); // Tanpa .clone(), function kedua akan menjadi error just_takes_a_variable(my_box); // karena Box bukanlah Copy }
Di awal-awal, memang sulit untuk membayangkan dimana dan di kasus seperti apa kita akan menggunakannya, tapi nantinya Anda akan sering menggunakan ini di Rust. Anda ingat bahwa &
digunakan pada str
karena compiler tidak mengetahui ukuran dari str
: karena panjangnya bisa berapa saja. Tapi reference &
selalu memiliki panjang yang sama, sehingga compiler bisa menggunakannya. Box
juga seperti itu. Juga, Anda bisa menggunakan *
pada Box
untuk mendapatkan valuenya, sama seperti &
:
fn main() { let my_box = Box::new(1); // Ini adalah Box<i32> let an_integer = *my_box; // Ini adalah i32 println!("{:?}", my_box); println!("{:?}", an_integer); }
Inilah mengapa Box disebut sebagai "smart pointer", karena ia mirip dengan reference &
(semacam pointer) namun bisa melakukan lebih banyak hal lainnya.
Anda juga bisa menggunakan Box untuk membuat struct dengan struct yang sama didalamnya. ini biasa disebut sebagai rekursi, yang berarti bahwa di dalam Struct A, mungkin ada field yang berisi Struct A pula. Terkadang Anda bisa menggunakan Box untuk membuat linked lists, meskipun list ini tidak begitu populer untuk digunakan di Rust. Jika Anda ingin membuat struct yang rekursif, Anda bisa menggunakan Box
. Inilah yang terjadi apabila Anda membuat sebuah struct yang rekursif tanpa menggunakan Box
:
#![allow(unused)] fn main() { struct List { item: Option<List>, // ⚠️ } }
List
di atas memiliki satu item, yang typenya adalah Some<List>
(list lainnya), atau None
. Karena kita bisa memilih None
, ia tidak akan melakukan rekursi terus-menerus. Tetapi compiler tetap tidak mengetahui ukurannya:
error[E0072]: recursive type `List` has infinite size
--> src\main.rs:16:1
|
16 | struct List {
| ^^^^^^^^^^^ recursive type has infinite size
17 | item: Option<List>,
| ------------------ recursive without indirection
|
= help: insert indirection (e.g., a `Box`, `Rc`, or `&`) at some point to make `List` representable
Anda bisa melihat, bahkan compiler menyarankan untuk mencoba menggunakan Box
. Jadi, mari kita coba gunakan Box
:
struct List { item: Option<Box<List>>, } fn main() {}
Sekarang compiler menerima List
tersebut, karena ia berada di dalam Box
, dan compiler mengetahui ukuran dari Box
. Berikut kita coba code di bawah ini:
struct List { item: Option<Box<List>>, } impl List { fn new() -> List { List { item: Some(Box::new(List { item: None })), } } } fn main() { let mut my_list = List::new(); }
Meskipun kita tidak memasukkan data apapun, ia terlihat agak rumit, dan Rust tidak terlalu sering menggunakan pattern/pola sepert ini. Ini dikarenakan Rust memiliki aturan yang sangat ketat mengenai borrowing dan ownership, seperti yang dari awal kita ketahui. Tapi jika Anda ingin membuat list seperti ini (linked list), Box
bisa digunakan untuk membuatnya.
Box
juga memungkinkan Anda untuk menggunakan std::mem::drop
, karena ia diletakkan di heap. Dan terkadang ini sangat membantu.