Rust memperlakuan bagian-bagian kode sebagai sebuah objek. Kita bisa memindahkan hak milik atau hanya meminjamnya. Pengaturan ini memastikan keamanan serta efisiensi memori dari program yang akan kita kembangkan.
- Pendahuluan
- Cakupan Variabel
- Transfer Ownership Dengan Fungsi
- Transfer Ownership Dengan Return Value
- Menggembalikan Lebih Dari Satu Nilai
- Penutupan
Pendahuluan
Pada beberapa bahasa pemrograman seperti C++, Java, dan C# melakukan manajemen memori menggunakan fitur garbage collection atau pengaturan manual. Sedangkan rust menggunakan pendekatan ownership.
Aturan umum ownership:
- Setiap value hanya memiliki satu owner.
- Setiap value pasti memiliki owner.
- Ketika value berada pada luar jagkauan, value tidak berlaku.
Ownership adalah serangkaian aturan mengenai pengelolaan memori pada program yang ditulis dengan bahasa rust. Memori atau biasa disebut dengan RAM haruslah dialokasikan dengan bijak agar terhindar dari masalah.
fn main() {
let x = 2 + 5; // tugas 1
println!("{x}"); // tugas 2
}
Contoh 1.0
Program Rust yang dijalankan akan menggunakan RAM agar perintah atau tugas dapat diselesaikan. Perhatikan pada contoh 1.0 terdapat dua tugas yang harus dilakukan oleh RAM.
Tugas pertama adalah let x = 2 + 5; dengan melakukan penjumlahan 2 + 5 = 7. Kemudian dilanjutkan dengan tugas kedua println!(“{x}”); untuk menampilkan variabel pada layar.
Langkah-langkah tersebut menggunakan RAM untuk menyelesaikan penjumlahan maupun menampilkan hasilnya pada layar komputer. Dengan mempelajari mengenai ownership maka kita akan mengetahui bagaimana kode rust menggunakan memori.
Stack
Stack adalah bagian dari memori yang melakukan penumpukan terhadap nilai atau value. Persyaratan utama suatu nilai di simpan pada stack adalah memiliki ukuran yang pasti dan sudah diketahui.

Gambar 1.2
Satu kata yang mewakili stack adalah LIFO (last in first out). Artinya data yang masuk terakhir, akan dikeluarkan terlebih dahulu.
Bayangkan sebuah tumpukan mangkuk di hajatan. Pastilah mangkuk yang diambil terlebih dahulu adalah bagian atas, bukan tengah atau bawah.
Heap

Heap tidak menumpuk value seperti stack. Melainkan mendistribusikan value sesuai dengan ruangan kosong yang tersedia pada memori. Kemudian memberikan informasi berupa alamat memori dengan pointer yang dapat disimpan pada stack.
Bayangkan sebuah hotel yang besar. Ketika kita ingin memesan satu kamar bersama tiga teman. Maka kita akan diberikan nomor ruangan kosong. Walaupun satu teman belum datang, nantinya dapat menyusul dengan melihat nomor ruangan.
Heap vs Stack
Rust memiliki dua jenis value string, yaitu string literal (&str) dan String. Dua tipe data ini akan menunjukan perbedaan stack dengan heap.
Data dengan jenis string literal sudah diketahui jenis dan ukurannya saat compile time. Maka dari itu &str disimpan pada bagian stack.
fn main() {
let x = "Halo ";
}
Contoh 1.1
Sayangnya tidak semua jenis proses cocok dengan string literal. Contohnya dalam guessing game, kita diharuskan untuk menerima input dari user berupa tebakan sebuah angka.
Tidak mungkin kita mengetahui angka berapa yang akan ditebak oleh user. Oleh karena itu, kita tidak menggunakan string literal melainkan String (awal kapital).
fn main() {
let mut x = String::from("Hello, ");
x.push_str("Word");
println!("{x}");
}
Contoh 1.2
Dengan menggunakan String kita dapat memasukan string literal sehingga variabel x bersifat mutable dan akan menerima teks tambahan “Word”. Program tersebut menggunakan heap dikarenakan variabel x menggunakan String.
Pengalokasian memori dilakukan ketika menggunakan syntax String::from. Setelah itu pointer akan menunjukan posisi ruang kosong dalam memori. Output dari contoh 1.3 adalah sebagai berikut.
Hello, Word
Contoh 1.3
Stack lebih cepat dikarenakan value yang dibutuhkan pastilah berada di bagian atas atau bawah. Heap memerlukan waktu untuk proses pencarian dengan pointer agar dapat mengakses posisi value secara aktual pada memori.
Cakupan Variabel
Variabel memiliki cakupan terbatas agar dapat digunakan (valid). Ketika berada pada luar jangkuan variabel tersebut menjadi tidak valid. Rust secara otomatis menggunakan drop untuk membersihkan variabel pada memori jika berada dalam luar jangkauan.
fn main() {// tidak valid
// tidak valid karena variabel belum di deklarasikan
let x = 2; // valid
// valid
// valid
// valid
} // tidak valid > drop
Contoh 1.4
Pada contoh 1.1 terdapat variabel x dengan nilai 2. Jika ingin menggunakan variabel tersebut untuk penjumlahan misalnya let y = x + 10; letakan kode tersebut dalam jangkauan sehingga bersifat valid.
Kurung kurawal menandakan akhir dari fungsi main sehingga variabel x sudah tidak berlaku. Ketika memaksakan memanggil variabel x di luar jangkauan, error.
Invaliditas Variabel
fn main() {
let x = String::from("Word"); //invalid
let y = x; //valid
println!("{}", y);
}
Contoh 1.5
Berdasarkan tabel tersebut dapat diketahui” bahwa variabel x disandingkan dengan String, yaitu “Word“. Secara eksplisit berarti String disimpan pada variabel x kemudian dikopi untuk variabel y.
Proses pada memori tidak berjalan sedemikian rupa, tetapi menggunakan prinsip stack dan heap. Ingat bahwa String pada rust menggunakan heap sedangkan pointer dapat disimpan pada stack karena ukurannya sudah diketahui.

Gambar 1.4
Gambar tersebut menunjukan 3 poin utama, yaitu:
- Pointer yang akan menunjukan posisi aktual data pada heap.
- Lenght adalah jumlah memori (bytes) yang telah digunakan oleh program.
- Capacity adalah total memori yang dialokasikan untuk program.
Saat kita melakukan let y = x; yang terjadi adalah seperti gambar 1.5.

Gambar 1.5
Kita tidak mengkopi posisi aktual data pada heap. Melainkan melakukan kopi pointer, lenght, dan capacity. Langkah tersebut pada rust disebut dengan move.
Masalah yang terjadi dengan model seperti gambar 1.5 adalah terjadi pembersihan memori lebih dari satu kali. Bayangkan ketika variabel x sudah diluar jangkauan maka rust akan membersihkan memori secara otomatis dengan drop.
Setelah itu variabel y juga akan membesihkan memori pada alamat memori yang sama dalam heap. Terjadilah double free error. Hal ini akan merambat pada korupsi memori dan kestabilan program.

Gambar 1.6
Untuk mengatasi masalah tersebut rust menjadikan variabel x invalid sehingga kita tidak bisa memanggil atau menggunakan variabel x setelah variabel y dideklarasikan. Dengan demikian pada saat variabel y sudah berada di luar jangkauan pembersihan memori hanya dilakukan satu kali saja.
error[E0382]: borrow of moved value: `x`
--> src/main.rs:6:26
|
3 | let x = String::from("Word"); //invalid
| - move occurs because `x` has type `String`, which does not implement the `Copy` trait
4 | let y = x; //valid
| - value moved here
5 |
6 | println!("{} {}", y, x);
| ^ value borrowed here after move
|
= note: this error originates in the macro `$crate::format_args_nl` which comes from the expansion of the macro `println` (in Nightly builds, run with -Z macro-backtrace for more info)
help: consider cloning the value if the performance cost is acceptable
|
4 | let y = x.clone(); //valid
| ++++++++
Contoh 1.6
Contoh di atas menunjukan ketika varibel x sudah dipindahkan ownershipnya pada variabel y lalu kita memaksa untuk memanggil x yang pastinya berstatus invalid.
Fungsi clone
fn main() {
let x: String = String::from("Word"); //valid
let y: String = x.clone(); //valid
println!("{} {}", y, y);
}
Contoh 1.7
Ketika kita menggunakan syntax clone() maka program akan melakukan kopi data pada heap. Hal ini tentunya akan memakai resource memori lebih besar karena terdapat dua tempat yang diisi oleh String dengan nilai yang sama pada memori.

Gambar 1.7
Fungsi copy
fn main () {
let x: i32 = 24;
let y: i32 = x;
println!("{}, {}", x, y);
}
Contoh 1.8
Pada kode di atas mekanisme yang terjadi pada memori berbeda dengan contoh sebelumnya. Hal ini dikarenakan U32/Un, F32/Fn, boolean, char, dan tuples berada dalam stack. Rust memiliki anotasi copy sehingga tidak diperlukan duplikasi data seperti heap.
Baik melakukan clone( ) atau tidak, tetap valid. Perhatikan output dari contoh 1.7 sebagai berikut.
24 24
Contoh 1.9
Transfer Ownership Dengan Fungsi
fn main() {
// Deklarasi variabel
let x: String = String::from("Foo");
// Variabel x valid
// Variabel x sudah dipindahkan pada fungsi
mengambil_kepemilikan_x(x);
// Variabel x tidak valid
// Deklarasi variabel y dengan tipe i32
// Disimpan dalam stack sehingga akan terus valid
// Walaupun kepemilikan sudah dipindahkan
let y: i32 = 20;
//Variabel y telah berpindah
mengambil_y(y);
// Menggunakan variabel y
print!("{}", y);
}
fn mengambil_kepemilikan_x(x_pindah_kesini: String) {
println!("{}", x_pindah_kesini);
}
fn mengambil_y (y_pindah_kesini: i32) {
println!("{}", y_pindah_kesini);
}Contoh 2.0
Secara gamblang kode tersebut menunjukan proses transfer ownership. Pertama, variabel x memiliki tipe data String sehingga menggunakan heap memori.
Kemudian sebuah fungsi dipanggil dengan parameter variabel x. Hal ini menyebabkan nilai dari x telah ditransfer pada fungsi mengambil_kepemilikan_x.
Jika kita berusaha memanggil variabel x setelah baris pemanggilan fungsi maka akan terjadi error, invalid. Sebaliknya pada variabel y dengan tipe data i32. Berlaku anotasi copy sehingga baik sebelum fungsi dipanggil atau sesudah variabel y tetap valid.
Transfer Ownership Dengan Return Value
fn main () {
// Variabel x akan mendapatkan ownership dari return value fn memberikan_ownership
let x: String = memberikan_ownership();
// Variabel a dideklarasikan
let a: String = String::from("Foo");
// Variabel a valid
// Mengambil ownership variabel a menjadi milik fn pengembalian_ownership
// Mengambil ownership fungsi menjadi milik variabel b
// a > fn > b
let b: String= pengembalian_ownership(a);
print!("{} {}", x, b,);
}
fn memberikan_ownership() -> String {
let y: String = String::from("Hua");
y
}
fn pengembalian_ownership (ownership_kesini: String) -> String {
ownership_kesini
}
Contoh 2.1
Terdapat pengembalian nilai atau return value dari fn memberikan_ownership() pada variabel x. Pengembalian suatu nilai ditunjukan dengan y tanpa semi colon “;“. Sekaligus menjadi proses transfer ownership.
Nilai dari variabel a akan dipindahkan pada fn memberikan_ownership() untuk kemudian dikembalikan ownershipnya pada variabel b.
Menggembalikan Lebih Dari Satu Nilai
fn main(){
// Variabel x telah dideklarasikan
let x: String = String::from("Foo!");
// Ownership variabel x dipindahkan pada fn meminjam_nilai
// Jumlah dari return value adala dua dengan tuples
// x > fn >y
let (y, len) = meminjam_nilai(x);
println!("{} memiliki panjang {} ", y, len);
}
fn meminjam_nilai (z: String) -> (String, usize) {
let panjang_nilai: usize = z.len();
(z, panjang_nilai)
}
Contoh 2.2
Pada kode tersebut kita mengembalikan dua data sekaligus dengan tuples, yaitu usize dan tuples. Ownership variabel x dipindahkan pada fungsi fn meminjam_nilai pada variabel z.
Setelah itu kita membuat sebuah variabel baru dengan nama panjang_nilai dengan syntax len(). Kemudian dibuat sebuah expression untuk mengembalikan dua nilai sekaligus (z, panjang_nilai).
Oleh karena itu, ketika data dalam tuple tersebut dipanggil dengan variabel baru dalam tuples, yaitu y dan len munculah nilai yang diinginkan. Hal itu terjadi karena ownership telah ditransfer dari variabel panjang_nilai dan z.
Penutupan
Mempelajari ownership rust tentunya membutuhkan effort lebih. Tetapi dengan semangat yang tinggi kita pasi bisa dan dapat menemukan beberapa hal menarik terutama bagi saya yang nihil pengalaman programming.