Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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.

Filename: src/main.rs
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");
}
Listing 15-14: Sebuah struct CustomSmartPointer yang mengimplementasikan trait Drop di mana kita bakal menaruh kode cleanup kita

Trait 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.

Filename: src/main.rs
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");
}
Listing 15-15: Mencoba memanggil method drop dari trait Drop secara manual untuk cleanup lebih awal

Pas 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.

Filename: src/main.rs
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");
}
Listing 15-16: Memanggil std::mem::drop buat me-drop secara eksplisit sebuah nilai sebelum dia keluar dari scope

Menjalankan 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.