Pada sebagian besar bahasa pemrograman, menyalin sebuah variabel ke variabel lain adalah kebiasaan yang tanpa konsekuensi: $a = $b di PHP atau b = a di Python tidak membuat variabel lama rusak. Di Rust, untuk tipe yang menyimpan data di heap, penugasan semacam itu justru memindahkan kepemilikan — variabel lama dinyatakan tidak valid oleh kompilator. Perilaku ini bernama move semantics, dan pemahaman yang kokoh tentangnya adalah syarat mutlak sebelum kita bisa membaca kode Rust dengan nyaman. Pada Chapter ini kita akan melihat mengapa Rust memilih desain ini, bagaimana move bekerja di balik layar, dan seperti apa rasa pesan error ketika kita melanggarnya.
Masalah yang Ingin Dicegah oleh Move #
Untuk memahami move, kita perlu kembali sebentar ke struktur String dari Chapter pertama. Variabel String di stack berisi tiga data: pointer ke isi teks di heap, panjang, dan kapasitas. Sekarang bayangkan jika penugasan let b = a; hanya menyalin tiga data kecil itu tanpa aturan apa pun. Akibatnya, dua variabel berbeda memegang pointer ke satu wilayah heap yang sama.
Bahaya muncul saat kedua variabel berakhir: masing-masing pemilik menjalankan drop, dan wilayah heap yang sama dikembalikan dua kali ke allocator. Galat klasik ini bernama double free dan dapat merusak kondisi internal allocator — memicu crash atau, lebih buruk, celah keamanan. Bahasa dengan garbage collector menghindarinya dengan menunggu sampai semua referensi mati; Rust menghindarinya dengan jalan yang lebih tegas: pada saat penugasan, kepemilikan berpindah dan variabel lama divalidasi. Dengan begitu, saat scope berakhir, hanya ada satu pihak yang berhak memulangkan heap.
Perpindahan Kepemilikan pada Assignment #
Mari kita lihat move dalam bentuk paling murninya:
fn main() {
let owner = String::from("Belajar Rust");
let new_owner = owner;
println!("Pemilik sekarang: {}", new_owner);
}Output yang Dihasilkan:
Pemilik sekarang: Belajar RustKode di atas berjalan lancar karena kita hanya memakai variabel yang sah. Mari kita bedah perpindahan yang terjadi:
- Baris 2 (
let owner = ...):ownermenjadi pemilik teks"Belajar Rust". Stack menyimpan tiga data pengelola, dan heap menyimpan isi teksnya. - Baris 3 (
let new_owner = owner;): inilah momen move. Rust menyalin tiga data pengelola di stack ke slot miliknew_owner, lalu menandaiownersebagai sudah dipindahkan (moved). Perlu dicatat: isi teks di heap tidak tersalin dan tidak berpindah tempat — yang berpindah adalah status kepemilikan. - Baris 5 (
println! ...): kita hanya mencetaknew_owner. Nilaiownersengaja tidak kita sentuh lagi, sehingga kompilator tidak menemukan pelanggaran.
Alur kepemilikan pada contoh di atas dapat digambarkan sebagai berikut:
Diagram tersebut menegaskan dua hal. Pertama, panah pertama bukan penyalinan data heap, melainkan serah terima tanggung jawab dari owner ke new_owner. Kedua, karena owner sudah tidak valid, hanya ada satu pihak yang menjalankan drop di akhir — celah double free tertutup sejak awal.
Kode yang Gagal: Memakai Nilai Setelah Dipindahkan #
Sekarang kita uji batasnya. Kode berikut sengaja memakai product_name setelah nilainya dipindahkan ke second_name — kode ini tidak dapat dikompilasi:
fn main() {
let product_name = String::from("Kopi Gayo");
let second_name = product_name;
println!("Produk asli: {}", product_name);
println!("Produk salinan: {}", second_name);
}Kompilasi dengan rustc --edition 2021 main.rs akan berakhir dengan kegagalan berikut.
Pesan error kompilator:
error[E0382]: borrow of moved value: `product_name`
--> main.rs:5:33
|
2 | let product_name = String::from("Kopi Gayo");
| ------------ move occurs because `product_name` has type `String`, which does not implement the `Copy` trait
3 | let second_name = product_name;
| ------------ value moved here
4 |
5 | println!("Produk asli: {}", product_name);
| ^^^^^^^^^^^^ value borrowed here after move
|
help: consider cloning the value if the performance cost is acceptable
|
3 | let second_name = product_name.clone();
| ++++++++
error: aborting due to 1 previous error
For more information about this error, try `rustc --explain E0382`.Pesan E0382 di atas sangat informatif dan layak dibaca pelan-pelan karena kita akan bertemu bentuk serupa berkali-kali:
move occurs because 'product_name' has type 'String', which does not implement the 'Copy' trait: kompilator menjelaskan alasan perpindahan —Stringbukan tipe murah yang bisa disalin otomatis. TraitCopydan lawannyaclone()akan kita bahas tuntas pada Chapter berikutnya.value moved herepada baris 3: penunjuk ke baris tempat kepemilikan berpindah.value borrowed here after movepada baris 5: penunjuk ke kejadian pelanggaran — kita memakaiproduct_nameyang statusnya sudah tidak sah.help: consider cloning ...: usulan perbaikan berupaproduct_name.clone()— jalan keluar sah apabila kita memang butuh dua salinan data.
Move Terjadi pada Banyak Kejadian, Bukan Hanya Assignment #
Move bukan perilaku khusus operator =. Setidaknya tiga kejadian lain memicu perpindahan kepemilikan untuk tipe non-Copy:
- Mengirim nilai sebagai argumen fungsi:
print_message(product_name);memindahkan kepemilikan ke parameter di dalam fungsi — pembahasan lengkapnya ada pada Chapter Ownership di Fungsi. - Mengembalikan nilai dari fungsi: kepemilikan berpindah dari dalam fungsi ke variabel penampung di pemanggil.
- Memasukkan nilai ke dalam struktur data koleksi seperti
Vec, yang akan kita temui pada section Koleksi.
Pola yang sama berlaku di semua kejadian tersebut: ada satu titik serah terima, variabel sumber berhenti valid, dan tanggung jawab pembersihan mengikuti pemilik baru.
Ringkasan #
Move semantics membuat penugasan tipe heap menjadi serah terima kepemilikan: variabel lama divalidasi, variabel baru mengambil alih, dan drop hanya dijalankan satu kali oleh pemilik yang sah. Kita telah melihat diagram alur kepemilikan, membaca pesan error E0382 sampai ke saran perbaikannya, dan mencatat bahwa pemanggilan fungsi serta nilai kembalian juga memicu move. Pertanyaan berikutnya: adakah tipe yang tidak ikut aturan ini, dan bagaimana cara membuat salinan penuh ketika dua salinan benar-benar dibutuhkan? Jawabannya ada pada Copy dan Clone.