Ownership di Rust
Ownership (kepemilikan) adalah ide yang membuat Rust benar-benar Rust. Dengan ownership, Rust menjamin keamanan memori tanpa garbage collector dan tanpa kamu harus membebaskan memori secara manual. Awalnya memang terasa asing, dan itu wajar: hampir semua orang merasakannya. Tapi ide intinya sederhana. Kita bangun pelan-pelan, ya.
Masalah yang diselesaikan ownership
Section titled “Masalah yang diselesaikan ownership”Saat program berjalan, ia menyimpan data di memori. Pada suatu saat memori itu harus dibersihkan. Kalau tidak, program mengalami kebocoran memori (memory leak) dan lama-lama crash. Ada dua cara umum yang dipakai bahasa pemrograman untuk menangani ini:
- Manual (C/C++): kamu sendiri yang membebaskan memori. Lupa, memori bocor. Terlalu cepat, program crash dan muncul celah keamanan.
- Garbage collector (Java, Go, Python): sebuah proses di latar belakang membersihkannya untukmu. Praktis, tapi ada harga yang dibayar dari sisi performa dan prediktabilitas.
Rust mengambil jalan ketiga: ia menentukan saat kompilasi kapan tepatnya setiap nilai harus dibersihkan, dengan seperangkat aturan tentang ownership. Tidak perlu membebaskan memori manual, tidak ada garbage collector.
Tiga aturan ownership
Section titled “Tiga aturan ownership”Inilah seluruh sistemnya. Semua hal lain mengikuti dari tiga aturan ini:
- Setiap nilai punya satu owner (pemilik), yaitu variabel yang “memiliki” nilai itu.
- Hanya boleh ada satu owner pada satu waktu.
- Saat owner keluar dari scope (cakupan), nilainya otomatis dibersihkan.
Mari kita lihat setiap aturan beraksi.
Aturan 3: nilai dibuang di akhir scope
Section titled “Aturan 3: nilai dibuang di akhir scope”Scope adalah area di antara { dan }. Saat scope sebuah variabel berakhir, Rust otomatis membebaskan nilainya. Kamu tidak pernah menulis free():
fn main() { { let message = String::from("halo"); // message valid di sini println!("{message}"); } // <- scope berakhir, `message` otomatis dibersihkan
// println!("{message}"); // ❌ error: message sudah tidak ada di sini}Kamu tidak perlu membebaskan apa pun. Rust melakukannya tepat saat message keluar dari scope.
Aturan 1 & 2: “move”
Section titled “Aturan 1 & 2: “move””Nah, di sinilah mulai menarik. Perhatikan apa yang terjadi saat kamu memberikan satu variabel ke variabel lain:
fn main() { let s1 = String::from("halo"); let s2 = s1; // ownership PINDAH dari s1 ke s2
println!("{s2}"); // ✅ berhasil // println!("{s1}"); // ❌ error: s1 sudah di-move}Saat kamu menulis let s2 = s1, Rust tidak menyalin string-nya. Ia memindahkan (move) ownership dari s1 ke s2. Karena aturan 2 bilang hanya boleh ada satu owner, s1 sekarang tidak valid lagi. Kalau kamu mencoba memakainya, hasilnya:
error[E0382]: borrow of moved value: `s1`Pesan ini artinya kamu mencoba memakai s1 padahal nilainya sudah dipindahkan.
Bagaimana kalau aku ingin dua salinan?
Section titled “Bagaimana kalau aku ingin dua salinan?”Kalau kamu memang ingin salinan kedua yang benar-benar terpisah, panggil .clone():
fn main() { let s1 = String::from("halo"); let s2 = s1.clone(); // membuat salinan lengkap
println!("s1 = {s1}, s2 = {s2}"); // ✅ sekarang keduanya bisa dipakai}clone() sengaja dibuat eksplisit. Menyalin bisa mahal, jadi Rust ingin kamu memintanya secara terang-terangan.
Tipe sederhana disalin, bukan di-move
Section titled “Tipe sederhana disalin, bukan di-move”Mungkin kamu bertanya-tanya kenapa kode ini tidak error:
fn main() { let x = 5; let y = x; println!("x = {x}, y = {y}"); // ✅ keduanya bisa dipakai}Nilai kecil berukuran tetap seperti integer, float, boolean, dan char murah untuk disalin, jadi Rust langsung menyalinnya alih-alih memindahkannya. Sebagai pemula, pegangan praktisnya begini:
- Nilai sederhana (angka,
bool,char) → disalin. Variabel aslinya tetap valid. - Data yang dimiliki (seperti
String) → di-move. Variabel aslinya jadi tidak valid.
Ownership dan fungsi
Section titled “Ownership dan fungsi”Mengirim nilai ke sebuah fungsi juga memindahkannya, persis seperti memberikannya ke variabel lain:
fn take_ownership(s: String) { println!("Sekarang aku pemiliknya: {s}");} // s dibuang di sini
fn main() { let text = String::from("halo"); take_ownership(text); // println!("{text}"); // ❌ error: text sudah di-move ke dalam fungsi}Inilah alasan topik berikutnya, borrowing (peminjaman), ada. Borrowing membuat sebuah fungsi bisa memakai nilai tanpa mengambil ownership-nya, sehingga kamu masih bisa memakainya sesudahnya.
Latihan
Section titled “Latihan”Coba kerjakan sendiri dulu, di Rust Playground atau di proyek lokal. Error dari compiler itu hal biasa selama latihan: baca pesannya, biasanya ia menunjuk langsung ke tempat terjadinya move.
1. Pertahankan yang asli
Section titled “1. Pertahankan yang asli”Program ini tidak bisa dikompilasi. Perbaiki supaya ia mencetak draf -> draf v2, tanpa menghapus salah satu variabelnya.
fn main() { let original = String::from("draf"); let mut edited = original; edited.push_str(" v2"); println!("{original} -> {edited}");}error[E0382]: borrow of moved value: `original` --> src/main.rs:5:16 |2 | let original = String::from("draf"); | -------- move occurs because `original` has type `String`, which does not implement the `Copy` trait3 | let mut edited = original; | -------- value moved here4 | edited.push_str(" v2");5 | println!("{original} -> {edited}"); | ^^^^^^^^ value borrowed here after moveCompiler memberi tahu bahwa original sudah di-move di baris 3 (“value moved here”), lalu kamu mencoba memakainya lagi di baris 5.
Petunjuk: kamu butuh dua string yang terpisah, supaya mengubah yang satu tidak menyentuh yang lain.
Lihat solusi
fn main() { let original = String::from("draf"); let mut edited = original.clone(); // salinan lengkap yang terpisah edited.push_str(" v2"); println!("{original} -> {edited}");}Output:
draf -> draf v2let mut edited = original; memindahkan string-nya, jadi original tidak valid lagi. .clone() membuat string kedua dengan memorinya sendiri. Sekarang push_str hanya mengubah edited, dan original tetap berisi draf.
2. Kembalikan lagi
Section titled “2. Kembalikan lagi”Tulis fungsi add_signature yang mengambil ownership sebuah String, menambahkan " -- Ferris" di akhirnya, lalu mengembalikan string tersebut. Panggil dari main dan cetak hasilnya.
Petunjuk: sebuah fungsi bisa mengembalikan nilai yang ia terima. Tulis -> String di signature-nya, dan ambil parameternya sebagai mut text: String supaya kamu boleh mengubahnya.
Lihat solusi
fn add_signature(mut text: String) -> String { text.push_str(" -- Ferris"); text // ownership pindah kembali ke pemanggil}
fn main() { let letter = String::from("Sampai jumpa!"); let letter = add_signature(letter); println!("{letter}");}Output:
Sampai jumpa! -- FerrisOwnership pindah masuk ke fungsi lewat pemanggilan, lalu pindah keluar lagi lewat nilai kembalian. Kita menangkapnya di variabel letter yang baru (shadowing variabel yang lama). Cara ini berhasil, tapi mengoper nilai masuk-keluar terus-menerus lama-lama melelahkan. Justru masalah itulah yang diselesaikan oleh borrowing.
3. Siapa pemiliknya sekarang?
Section titled “3. Siapa pemiliknya sekarang?”Program ini tidak bisa dikompilasi. Perbaiki supaya ia mencetak Ana masuk ke daftar, lalu ["Ana"].
fn main() { let mut names = Vec::new(); let name = String::from("Ana"); names.push(name); println!("{name} masuk ke daftar"); println!("{:?}", names);}error[E0382]: borrow of moved value: `name` --> src/main.rs:5:16 |3 | let name = String::from("Ana"); | ---- move occurs because `name` has type `String`, which does not implement the `Copy` trait4 | names.push(name); | ---- value moved here5 | println!("{name} masuk ke daftar"); | ^^^^ value borrowed here after movePetunjuk: push mengambil ownership, sama seperti pemanggilan fungsi. Siapa pemilik string itu setelah baris 4? Pakai name sebelum terjadi move, atau tanya pemilik barunya.
Lihat solusi
fn main() { let mut names = Vec::new(); let name = String::from("Ana"); println!("{name} masuk ke daftar"); // pakai selagi masih milik kita names.push(name); // sekarang vektor yang memilikinya println!("{:?}", names);}Output:
Ana masuk ke daftar["Ana"]Setelah names.push(name), vektor itulah pemiliknya, dan name tidak bisa dipakai lagi. Mencetak lebih dulu adalah salah satu solusinya. Cara lain: biarkan urutannya seperti semula, tapi cetak names[0], karena string-nya masih ada di dalam vektor. Clone juga bisa, tapi di sini tidak ada alasan untuk menyalin.
Ringkasan
Section titled “Ringkasan”- Ownership membuat Rust bisa mengelola memori dengan aman tanpa garbage collector.
- Tiga aturan: satu owner per nilai, satu owner pada satu waktu, dan nilai dibuang saat owner keluar dari scope.
- Memberikan data yang dimiliki (seperti
String) ke variabel lain akan memindahkannya (move), dan variabel aslinya jadi tidak valid. - Pakai
.clone()untuk salinan yang terpisah; tipe sederhana (angka,bool,char) disalin otomatis. - Mengirim nilai ke fungsi juga memindahkannya, dan itulah yang membawa kita ke borrowing.
Kuis singkat
Section titled “Kuis singkat”Kuis singkat
1. Berapa banyak owner yang boleh dimiliki sebuah nilai pada satu waktu di Rust?
2. Method apa yang kamu panggil untuk membuat salinan terpisah sebuah String, alih-alih memindahkannya?
3. Tipe mana yang otomatis disalin, bukan di-move, saat diberikan ke variabel lain?
4. Apa yang terjadi saat kamu mengirim String ke fungsi yang menerimanya by value (bukan lewat referensi)?
5. Menurut halaman ini, kapan Rust otomatis membersihkan (drop) sebuah nilai?
Skor: 0 / 5
Bacaan lebih lanjut
Section titled “Bacaan lebih lanjut”- What Is Ownership? — The Rust Book (bahasa Inggris): bab resminya, lengkap dengan gambar bentuk
Stringdi memori dan kenapa move tidak menyalinnya. - Ownership and moves — Rust by Example (bahasa Inggris): contoh singkat yang bisa dijalankan tentang move, termasuk move ke dalam fungsi.
- RAII — Rust by Example (bahasa Inggris): bagaimana nilai dibersihkan saat keluar dari scope, dan di mana peran trait
Drop. Copy— dokumentasi standard library (bahasa Inggris): tipe mana yang disalin alih-alih di-move, dan beda antaraCopydanClone.- Rust track — Exercism (bahasa Inggris): soal latihan gratis dengan umpan balik, kalau kamu ingin latihan lebih banyak dari yang ada di halaman ini.