Mutable references
Jika Anda ingin menggunakan reference untuk mengubah data, Anda bisa menggunakan mutable reference. Untuk mengunakan mutable reference, Anda hanya perlu menuliskan &mut
menggantikan &
.
fn main() { let mut my_number = 8; // jangan lupa untuk menuliskan mut disini! let num_ref = &mut my_number; }
Jadi, apa type dari kedua variabel tersebut? my_number
menggunakan type i32
, dan num_ref
bertype &mut i32
(kita bisa menyebutnya "mutable reference ke sebuah typei32
").
Mari kita gunakan num_ref
untuk menambahkan 10 ke my_number
. Tapi Anda tidak bisa menulis num_ref += 10
, karena value dari num_ref
tidak bertype i32
, melainkan &i32
. Value yang asli, ia berada pada i32
. Untuk mengakses tempat dimana value aslinya tersimpan, kita menggunakan *
. *
, yang berarti "Saya tidak mau referencenya, yang saya inginkan adalah value aslinya". Dengan kata lain, setiap *
adalah lawan dari &
. Dan juga, setiap penggunaan *
akan menghapus sebuah &
.
fn main() { let mut my_number = 8; let num_ref = &mut my_number; *num_ref += 10; // Use * to change the i32 value. println!("{}", my_number); let second_number = 800; let triple_reference = &&&second_number; println!("Second_number = triple_reference? {}", second_number == ***triple_reference); }
Hasilnya adalah seperti berikut:
18
Second_number = triple_reference? true
Karena di saat kita menggunakan &
disebut sebagai "referencing", maka menggunakan *
disebut sebagai "dereferencing".
Rust memiliki 2 aturan untuk mutable dan immutable reference. Aturan ini sangatlah penting, dan juga mudah diingat karena aturan ini sangatlah masuk akal.
- Aturan 1: Jika kita hanya memiliki immutable references, kita bisa memilikinya sebanyak yang kita mau. 1, boleh. 3, juga boleh. 1000, juga tidak apa. Tidak masalah.
- Aturan 2: Jika Anda memiliki mutable reference, Anda hanya boleh memiliki 1 saja. Juga, Anda tidak bisa memiliki sebuah immutable reference dan sebuah mutable reference sekaligus.
Ini dikarenakan mutable references bisa mengubah data. Anda bisa saja mendapatkan problem jika Anda mengubah data di saat references yang lain sedang membaca data tersebut.
Cara yang baik untuk memahami hal ini adalah dengan cara membayangkan tentang presentasi Powerpoint.
Situasi pertama ini adalah tentang kasus hanya 1 mutable reference.
Situasi pertama: Seorang pegawai sedang membuat presentasi Powerpoint. Ia ingin Managernya membantunya untuk membuatnya. Pegawai tersebut memberikan informasi login komputernya ke Managernya, dan meminta bantuan pada Manager tersebut untuk mengedit presentasinya. Sekarang Managernya memiliki sebuah "mutable reference" yang merujuk kepada presentasi si pegawai. Si Manager bisa membuat perubahan yang dia inginkan di presentasi tersebut, dan logout dari komputer tersebut setelahnya. Hal ini boleh dilakukan, karena tidak ada orang lain yang sedang melihat presentasi tersebut.
Situasi kedua ini adalah tentang kasus yang ada hanya immutable references.
Situasi kedua: Si Pegawai menunjukkan presentasinya 100 orang. Semua 100 orang ini tentunya bisa melihat presentasi si Pegawai. Mereka semua memiliki "immutable reference" yang merujuk kepada presentasi si pegawai. Hal ini boleh dilakukan, karena mereka bisa melihatnya namun tidak ada seorang pun yang bisa mengubah presentasi tersebut.
Situasi ketiga ini adalah dimana datangnya masalah.
Situasi ketiga: Si pegawai memberikan akses login komputernya ke Managernya. Managernya sekarang memiliki "mutable reference". Kemudian si Pegawai tadi menujukkan presentasinya ke 100 orang, di saat manager masih login di komputer tersebut . Ini sama sekali tidak baik, karena manager masih dalam kondisi login dan bisa melakukan apapun. Bisa saja si manager lupa bahwa ia sedang menggunakan komputer orang lain dan tidak sengaja sedang menulis email untuk ibunya! Jika itu terjadi, maka 100 orang yang sedang melihat presentasi tadi justru melihat si Manager menulis email tersebut. Hal seperti inilah yang tidak kita harapkan.
Ini adalah contoh code yang mana ada mutable borrow (mutable reference) sekaligus dengan immutable borrow (immutable reference) yang merujuk ke satu variabel:
fn main() { let mut number = 10; let number_ref = &number; let number_change = &mut number; *number_change += 10; println!("{}", number_ref); // ⚠️ }
Maka compiler akan mencetak pesan yang cukup membantu untuk menunjukkan kita letak masalahnya.
error[E0502]: cannot borrow `number` as mutable because it is also borrowed as immutable
--> src\main.rs:4:25
|
3 | let number_ref = &number;
| ------- immutable borrow occurs here
4 | let number_change = &mut number;
| ^^^^^^^^^^^ mutable borrow occurs here
5 | *number_change += 10;
6 | println!("{}", number_ref);
| ---------- immutable borrow later used here
Akan tetapi, code dibawah ini berjalan. Kira-kira kenapa?
fn main() { let mut number = 10; let number_change = &mut number; // buat sebuah mutable reference *number_change += 10; // gunakan mutable reference untuk menambahkan 10 let number_ref = &number; // buat sebuah immutable reference println!("{}", number_ref); // cetak immutable reference }
Dan tercetak 20
tanpa ada problem apapun. Ini bisa bekerja karena compiler cukup cerdas untuk mengerti code yang kita tulis. Compiler tahu bahwa kita menggunakan number_change
untuk mengubah number
, namun kita tidak menggunakan number_change
itu lagi setelahnya. Sehingga hal seperti ini bisa ditolerir dan tidak bermasalah. Kita tidak menggunakan immutable reference dan mutable references secara bersamaan.
Pada versi awal Rust, code seperti ini akan menghasilkan error, namun sekarang ini compiler Rust jauh lebih cerdas. Ia tidak hanya memahami apa yang kita tuliskan, tapi juga paham bagaimana kita menuliskan codenya secara keseluruhan.
Shadowing again
Masih ingat di saat kita menyebutkan bahwa shadowing tidak akan menghancurkan sebuah value, akan tetapi memblocknya? Sekarang kita bisa menggunakan references untuk melihatnya.
fn main() { let country = String::from("Austria"); let country_ref = &country; let country = 8; println!("{}, {}", country_ref, country); }
Yang mana yang merupakan hasil cetaknya? Austria, 8
atau 8, 8
?
Jawabannya adalah Austria, 8
. Pertama, kita deklarasikan String
yang bernama country
. Kemudian kita membuat reference country_ref
yang merujuk ke string tersebut. Kemudian kita shadow variabel country menggunakan 8, yang mana ia bertype i32
. Variabel country
yang pertama tidak hangus, jadinya country_ref
tetap berisi "Austria", bukan "8". Di bawah ini adalah code yang sama dengan komentar yang menunjukkan bagaimana code tersebut bekerja:
fn main() { let country = String::from("Austria"); // Sekarang kita memiliki String yang bernama country let country_ref = &country; // country_ref adalah reference ke String yang kita buat tadi. Dan ini tidak akan berubah let country = 8; // Sekarang kita memiliki variabel yang bernama country lagi dengan type i8. Tapi variabel ini tidak berhubungan dengan variabel country yang ada di awal, ataupun dengan variabel country_ref println!("{}, {}", country_ref, country); // country_ref tetap me-refer (merujuk) ke data String::from("Austria"). }