Menjalankan Kode saat Proses Pembersihan (Cleanup) dengan Trait Drop
Trait kedua yang penting buat pola smart pointer adalah Drop, yang
membiarkan kita mengkustomisasi apa yang terjadi saat sebuah nilai bakal
keluar dari scope. Kita bisa menyediakan sebuah implementasi buat trait
Drop di tipe apa pun, dan kode tersebut bisa dipakai buat melepaskan
(release) resources kayak file atau koneksi jaringan.
Kita ngenalin Drop di konteks smart pointers karena fungsionalitas dari
trait Drop hampir selalu dipakai pas kita mengimplementasikan sebuah smart
pointer. Misalnya, saat sebuah Box<T> di-drop, dia bakal men-deallocate
(melepaskan) ruang di heap yang ditunjuk sama box tersebut.
Di beberapa bahasa, buat tipe-tipe tertentu, si programmer harus memanggil kode buat membebaskan memori atau resources setiap kali mereka kelar memakai sebuah instance dari tipe-tipe tersebut. Contohnya termasuk file handles (pegangan file), sockets, sama locks. Kalau mereka lupa, sistem bisa jadi overloaded dan crash. Di Rust, kita bisa menentukan bahwa sekumpulan kode tertentu bakal dijalankan setiap kali sebuah nilai keluar dari scope, dan compiler bakal menyisipkan (insert) kode ini secara otomatis. Hasilnya, kita tidak perlu repot-repot menaruh kode cleanup (pembersihan) di mana-mana di dalam program setiap kali sebuah instance dari suatu tipe sudah selesai dipakai—dan kita tetap tidak bakal membocorkan (leak) resources!
Kita menentukan kode yang bakal jalan saat sebuah nilai keluar dari scope
dengan mengimplementasikan trait Drop. Trait Drop mewajibkan kita buat
mengimplementasikan satu method bernama drop yang menerima referensi
mutable ke self. Buat melihat kapan Rust memanggil drop, mari kita
mengimplementasikan drop dengan statements println! dulu buat sekarang.
Listing 15-14 menunjukkan sebuah struct CustomSmartPointer yang satu-satunya
fungsionalitas kustom yang dia punya adalah dia bakal mencetak Dropping CustomSmartPointer! saat instance-nya keluar dari scope, buat menunjukkan
kapan Rust menjalankan method drop.
struct CustomSmartPointer {
data: String,
}
impl Drop for CustomSmartPointer {
fn drop(&mut self) {
println!("Dropping CustomSmartPointer with data `{}`!", self.data);
}
}
fn main() {
let c = CustomSmartPointer {
data: String::from("my stuff"),
};
let d = CustomSmartPointer {
data: String::from("other stuff"),
};
println!("CustomSmartPointers created");
}
CustomSmartPointer yang mengimplementasikan trait Drop di mana kita bakal menaruh kode cleanup kitaTrait Drop sudah dimasukkan ke dalam prelude, jadi kita tidak perlu membawa
trait itu ke dalam scope. Kita mengimplementasikan trait Drop pada
CustomSmartPointer dan menyediakan sebuah implementasi buat method drop yang
memanggil println!. Body dari method drop adalah tempat di mana kita
bakal menaruh logika apa pun yang mau kita jalankan pas sebuah instance dari
tipe kita keluar dari scope. Kita mencetak sedikit teks di sini buat
mendemonstrasikan secara visual kapan Rust bakal memanggil drop.
Di main, kita bikin dua instance dari CustomSmartPointer lalu mencetak
CustomSmartPointers created. Di akhir main, instance-instance dari
CustomSmartPointer kita bakal keluar dari scope, dan Rust bakal memanggil
kode yang kita taruh di method drop, yang mana mencetak pesan terakhir kita.
Perhatikan bahwa kita tidak perlu memanggil method drop secara eksplisit.
Pas kita menjalankan program ini, kita bakal melihat output berikut:
$ cargo run
Compiling drop-example v0.1.0 (file:///projects/drop-example)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.60s
Running `target/debug/drop-example`
CustomSmartPointers created
Dropping CustomSmartPointer with data `other stuff`!
Dropping CustomSmartPointer with data `my stuff`!
Rust secara otomatis memanggil drop buat kita pas instance-instance kita keluar
dari scope, dan menjalankan kode yang sudah kita tentukan. Variabel-variabel
di-drop dalam urutan yang berlawanan dari pembuatannya, jadi d di-drop
sebelum c. Contoh ini tujuannya adalah buat ngasih kita panduan visual soal
gimana method drop itu bekerja; biasanya kita bakal menentukan kode cleanup
yang dibutuhkan sama tipe kita, ketimbang cuma pesan print biasa.
Sayangnya, menonaktifkan fungsionalitas drop otomatis ini tidak
gampang. Menonaktifkan drop biasanya juga tidak diperlukan; keseluruhan
poin dari trait Drop adalah supaya hal itu diurus secara otomatis. Tapi,
terkadang, kita mungkin pengen membersihkan (clean up) sebuah nilai lebih
awal. Salah satu contohnya adalah pas lagi memakai smart pointers yang
mengelola locks: kita mungkin pengen memaksa method drop yang bakal
melepaskan lock itu agar kode lain di scope yang sama bisa mendapatkan
lock tersebut. Rust tidak membiarkan kita buat memanggil method drop
dari trait Drop secara manual; sebaliknya, kita harus memanggil fungsi
std::mem::drop yang disediakan sama standard library kalau kita mau
memaksa sebuah nilai buat di-drop sebelum akhir dari scope-nya.
Kalau kita mencoba memanggil method drop dari trait Drop secara manual
dengan memodifikasi fungsi main dari Listing 15-14, seperti yang ditunjukkan
di Listing 15-15, kita bakal dapat error compiler.
struct CustomSmartPointer {
data: String,
}
impl Drop for CustomSmartPointer {
fn drop(&mut self) {
println!("Dropping CustomSmartPointer with data `{}`!", self.data);
}
}
fn main() {
let c = CustomSmartPointer {
data: String::from("some data"),
};
println!("CustomSmartPointer created");
c.drop();
println!("CustomSmartPointer dropped before the end of main");
}
drop dari trait Drop secara manual untuk cleanup lebih awalPas kita nyoba men-compile kode ini, kita bakal dapat error ini:
$ cargo run
Compiling drop-example v0.1.0 (file:///projects/drop-example)
error[E0040]: explicit use of destructor method
--> src/main.rs:16:7
|
16 | c.drop();
| ^^^^ explicit destructor calls not allowed
|
help: consider using `drop` function
|
16 - c.drop();
16 + drop(c);
|
For more information about this error, try `rustc --explain E0040`.
error: could not compile `drop-example` (bin "drop-example") due to 1 previous error
Pesan error ini menyatakan kalau kita tidak diizinkan buat memanggil drop
secara eksplisit. Pesan error ini memakai istilah destructor (destruktor),
yang mana adalah istilah pemrograman umum buat sebuah fungsi yang membersihkan
sebuah instance. Sebuah destructor analog (mirip) dengan sebuah constructor
(konstruktor), yang membikin sebuah instance. Fungsi drop di Rust adalah
salah satu destruktor tertentu.
Rust tidak membiarkan kita memanggil drop secara eksplisit karena Rust tetap
bakal secara otomatis memanggil drop pada nilainya di akhir dari main. Hal
ini bakal menyebabkan error double free karena Rust bakal mencoba membersihkan
nilai yang sama dua kali.
Kita tidak bisa menonaktifkan penyisipan drop secara otomatis pas sebuah nilai
keluar dari scope, dan kita tidak bisa memanggil method drop secara eksplisit.
Jadi, kalau kita butuh memaksa sebuah nilai buat dibersihkan lebih awal, kita
memakai fungsi std::mem::drop.
Fungsi std::mem::drop berbeda dari method drop yang ada di trait Drop.
Kita memanggilnya dengan meneruskan nilai yang mau kita paksa untuk di-drop
sebagai argumennya. Fungsi ini ada di dalam prelude, jadi kita bisa memodifikasi
main di Listing 15-15 buat memanggil fungsi drop tersebut, seperti yang
ditunjukkan di Listing 15-16.
struct CustomSmartPointer {
data: String,
}
impl Drop for CustomSmartPointer {
fn drop(&mut self) {
println!("Dropping CustomSmartPointer with data `{}`!", self.data);
}
}
fn main() {
let c = CustomSmartPointer {
data: String::from("some data"),
};
println!("CustomSmartPointer created");
drop(c);
println!("CustomSmartPointer dropped before the end of main");
}
std::mem::drop buat me-drop secara eksplisit sebuah nilai sebelum dia keluar dari scopeMenjalankan kode ini bakal mencetak yang berikut ini:
$ cargo run
Compiling drop-example v0.1.0 (file:///projects/drop-example)
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.73s
Running `target/debug/drop-example`
CustomSmartPointer created
Dropping CustomSmartPointer with data `some data`!
CustomSmartPointer dropped before the end of main
Teks Dropping CustomSmartPointer with data `some data`! dicetak di antara
teks CustomSmartPointer created. dan CustomSmartPointer dropped before the end of main., menunjukkan kalau kode method drop dipanggil
buat me-drop c pada titik tersebut.
Kita bisa memakai kode yang ditentukan di dalam implementasi trait Drop
pakai berbagai cara buat membikin cleanup jadi nyaman dan aman: misalnya,
kita bisa memakainya buat membikin memory allocator kita sendiri! Dengan trait
Drop dan sistem ownership di Rust, kita tidak perlu repot nginget-nginget
buat cleanup karena Rust ngelakuin itu secara otomatis.
Kita juga tidak perlu khawatir soal masalah-masalah yang timbul dari secara
tidak sengaja membersihkan nilai yang masih dipakai: sistem ownership yang
memastikan kalau referensi bakal selalu valid juga memastikan kalau drop
cuma bakal dipanggil satu kali pas nilainya memang sudah tidak dipakai lagi.
Sekarang setelah kita meneliti Box<T> dan beberapa karakteristik dari
smart pointers, mari kita lihat beberapa smart pointers lain yang didefinisikan
di standard library.