Konkurensi yang Bisa Diperluas (Extensible) dengan Trait Send dan Sync
Yang menarik, hampir semua fitur konkurensi yang udah kita bahas sejauh ini di bab ini adalah bagian dari standard library, bukan bagian dari bahasa Rust itu sendiri. Pilihan kita buat menangani konkurensi tidak cuma terbatas pada apa yang disediakan oleh bahasa atau standard library; kita bisa nulis fitur konkurensi kita sendiri atau memakai yang udah ditulis sama orang lain.
Namun, di antara konsep-konsep konkurensi utama yang tertanam (embedded) di
dalam bahasanya ketimbang di standard library adalah marker traits (trait
penanda) std::marker yaitu Send dan Sync.
Mengizinkan Transfer Kepemilikan Antar Threads dengan Send
Marker trait Send mengindikasikan bahwa kepemilikan (ownership) dari nilai
dengan tipe yang mengimplementasikan Send itu bisa ditransfer antar threads.
Hampir setiap tipe di Rust mengimplementasikan Send, tapi ada beberapa
pengecualian, termasuk Rc<T>: tipe ini tidak bisa mengimplementasikan Send
karena kalau kita meng-clone sebuah nilai Rc<T> lalu mencoba buat mentransfer
kepemilikan dari clone tersebut ke thread lain, kedua threads bisa aja
meng-update reference count di waktu yang bersamaan. Atas alasan inilah,
Rc<T> diimplementasikan buat dipakai di situasi single-threaded di mana kita
tidak mau membayar penalti performa (performance penalty) demi keamanan thread.
Oleh karena itu, sistem tipe dan trait bounds Rust memastikan kalau kita tidak
bakal bisa secara tidak sengaja mengirim nilai Rc<T> melintasi threads
dengan cara yang tidak aman. Pas kita mencoba melakukan ini di Listing 16-14,
kita dapat error the trait `Send` is not implemented for `Rc<Mutex<i32>>`.
Pas kita ganti jadi pakai Arc<T>, yang mana emang mengimplementasikan Send,
kodenya berhasil di-compile.
Tipe apa pun yang secara utuh disusun (composed entirely) dari tipe-tipe yang
mengimplementasikan Send bakal secara otomatis ditandai sebagai Send juga.
Hampir semua tipe primitif itu Send, kecuali untuk raw pointers (pointer
mentah), yang bakal kita bahas di Bab 20.
Mengizinkan Akses dari Banyak Threads dengan Sync
Marker trait Sync mengindikasikan kalau aman buat sebuah tipe yang
mengimplementasikan Sync untuk dirujuk (referenced) dari banyak threads.
Dengan kata lain, tipe T apa pun mengimplementasikan Sync kalau &T
(referensi immutable ke T) mengimplementasikan Send, yang berarti
referensi tersebut bisa dikirim dengan aman ke thread lain. Sama kayak Send,
semua tipe primitif mengimplementasikan Sync, dan tipe-tipe yang secara utuh
disusun dari tipe-tipe yang mengimplementasikan Sync juga bakal mengimplementasikan
Sync.
Smart pointer Rc<T> juga tidak mengimplementasikan Sync dengan alasan yang
sama kayak kenapa dia tidak mengimplementasikan Send. Tipe RefCell<T>
(yang kita bahas di Bab 15) dan keluarga dari tipe Cell<T> yang terkait juga
tidak mengimplementasikan Sync. Implementasi dari borrow checking yang
dilakukan RefCell<T> saat runtime itu tidak thread-safe (tidak aman di
lingkungan banyak utas). Smart pointer Mutex<T> mengimplementasikan Sync
dan bisa dipakai buat berbagi akses dengan banyak threads, seperti yang kita
lihat di “Berbagi Mutex<T> di Antara Beberapa Threads”.
Mengimplementasikan Send dan Sync secara Manual Itu Unsafe (Tidak Aman)
Karena tipe yang secara utuh disusun dari tipe-tipe lain yang mengimplementasikan
trait Send dan Sync itu juga otomatis mengimplementasikan Send dan Sync,
kita tidak perlu mengimplementasikan trait-trait tersebut secara manual. Sebagai
marker traits, mereka bahkan tidak punya method apa pun buat diimplementasikan.
Mereka cuma berguna buat memaksakan (enforcing) aturan baku (invariants) yang
berkaitan dengan konkurensi.
Mengimplementasikan trait-trait ini secara manual melibatkan penulisan kode Rust
yang unsafe. Kita bakal ngomongin soal memakai kode Rust yang unsafe di
Bab 20; buat sekarang, informasi pentingnya adalah bahwa membangun tipe konkuren
baru yang tidak disusun dari bagian-bagian yang Send dan Sync membutuhkan
pemikiran yang ekstra hati-hati buat mempertahankan jaminan keamanannya (safety
guarantees). “The Rustonomicon” punya lebih banyak informasi soal
jaminan-jaminan ini dan gimana cara mempertahankannya.
Ringkasan
Ini bukan terakhir kalinya kita bakal melihat konkurensi di buku ini: bab berikutnya berfokus pada pemrograman async, dan project di Bab 21 bakal memakai konsep-konsep di bab ini di dalam situasi yang lebih realistis ketimbang contoh-contoh kecil yang dibahas di sini.
Seperti yang disebutkan sebelumnya, karena cuma sebagian kecil dari cara Rust menangani konkurensi itu yang menjadi bagian dari bahasanya, banyak solusi konkurensi diimplementasikan dalam bentuk crates. Crate-crate ini berevolusi lebih cepat daripada standard library, jadi pastikan kita mencari secara online buat crates yang paling mutakhir (state-of-the-art) buat dipakai di situasi- situasi multithreaded.
Standard library Rust menyediakan channels buat pengiriman pesan (message
passing) dan tipe-tipe smart pointer, seperti Mutex<T> dan Arc<T>, yang
aman buat dipakai di konteks konkuren. Sistem tipe dan borrow checker memastikan
kalau kode yang memakai solusi-solusi ini tidak bakal berujung pada data races
atau referensi yang tidak valid. Begitu kita berhasil membikin kode kita bisa
di-compile, kita bisa bernapas lega karena dia bakal jalan dengan bahagia di atas
banyak threads tanpa jenis bugs yang susah dilacak kayak yang biasa terjadi
di bahasa pemrograman lain. Pemrograman konkuren bukan lagi konsep yang perlu
ditakutkan: maju terus dan bikin program kita konkuren tanpa rasa takut!