Rc

Rc adalah singkatan dar "reference counter". Kita semua sejauh ini sama-sama tahu, bahwa setiap variabel di Rust hanya bisa memiliki satu owner. Oleh karena hal itu, code dibawah ini tidak akan bekerja:

fn takes_a_string(input: String) {
    println!("It is: {}", input)
}

fn also_takes_a_string(input: String) {
    println!("It is: {}", input)
}

fn main() {
    let user_name = String::from("User MacUserson");

    takes_a_string(user_name);
    also_takes_a_string(user_name); // ⚠️
}

Setelah takes_a_string mengambil user_name, kita sama sekali tidak bisa menggunakannya lagi. Kita bisa saja menggunakan alternatif ini: Anda bisa memberi function tersebut dengan user_name.clone(). Tapi, terkadang sebuah variabel adalah bagian dari sebuah struct, dan mungkin Anda tidak bisa melakukan clone terhadap struct. Atau mungkin juga Stringnya terlampau panjang dan Anda tidak ingin menggunakan clone (karena menguras memori). Nah, alasan-alasan seperti inilah mengapa Rc digunakan, yang memungkinan Anda untuk memiliki lebih dari satu owner. Rc bisa dianalogikan seperti petugas yang mencatat kepemilikan: Rc siapa saja yang menulis ownership, dan seberapa banyak. Kemudian, setelah jumlah owner telah menjadi 0, variabel tersebut bisa menghilang.

Ini adalah bagaimana kita bisa menggunakan Rc. Pertama, bayangkan dua struct: satu bernama City, dan yang satunya bernama CityData. City memiliki informasi satu kota, dan CityData memasukkan semua kota ke dalam Vecs.

#[derive(Debug)]
struct City {
    name: String,
    population: u32,
    city_history: String,
}

#[derive(Debug)]
struct CityData {
    names: Vec<String>,
    histories: Vec<String>,
}

fn main() {
    let calgary = City {
        name: "Calgary".to_string(),
        population: 1_200_000,
           // Anggap saja String city_history ini sangat panjang
        city_history: "Calgary began as a fort called Fort Calgary that...".to_string(),
    };

    let canada_cities = CityData {
        names: vec![calgary.name], // Ini menggunakan calgary.name, yang mana lebih pendek
        histories: vec![calgary.city_history], // String yang ini sangatlah panjang
    };

    println!("Calgary's history is: {}", calgary.city_history);  // ⚠️
}

Tentu saja, code di atas tidak berjalan karena canada_cities yang memiliki datanya dan calgary tidak lagi memilikinya. Berikut adalah pesan errornya:

error[E0382]: borrow of moved value: `calgary.city_history`
  --> src\main.rs:27:42
   |
24 |         histories: vec![calgary.city_history], // But this String is very long
   |                         -------------------- value moved here
...
27 |     println!("Calgary's history is: {}", calgary.city_history);  // ⚠️
   |                                          ^^^^^^^^^^^^^^^^^^^^ value borrowed here after move
   |
   = note: move occurs because `calgary.city_history` has type `std::string::String`, which does not implement the `Copy` trait

Kita bisa menggunakan clone untuk bagian nama: names: vec![calgary.name.clone()]. Tapi kita tidak ingin menggunakan clone untuk city_history, karena ia terlalu panjang. Jadi kita bisa menggunakan Rc.

Tambahkan deklarasi use:

use std::rc::Rc;

fn main() {}

Kemudian letakkan Rc untuk membungkus type String.

use std::rc::Rc;

#[derive(Debug)]
struct City {
    name: String,
    population: u32,
    city_history: Rc<String>,
}

#[derive(Debug)]
struct CityData {
    names: Vec<String>,
    histories: Vec<Rc<String>>,
}

fn main() {}

Untuk menambahkan reference yang baru, Anda perlu melakukan clone terhadap Rc. Tapi, tunggu dulu, bukankah kita menghindari untuk menggunakan .clone()? Tidak sepenuhnya tepat: kita tidak ingin melakukan clone terhadap seluruh Stringnya. Tetapi yang di-clone adalah Rcnya. Clone dari Rc sebenarnya adalah melakukan clone terhadap pointer - yang mana itu benar-benar menghemat memori. Ini seperti menempelkan sticker nama (yang mana adalah nama setiap pemilik) ke sebuah kotak berisi buku-buku, untuk menunjukkan bahwa ada 2 orang yang memilikinya, daripada menggunakan kotak yang berbeda.

Kita bisa melakukan clone terhadap Rc yang bernama item dengan menggunakan item.clone() atau Rc::clone(&item). Jadinya, calgary.city_history memiliki 2 owners. Kita bisa mengetahui berapa banyak ownernya menggunakan Rc::strong_count(&item). Dan juga, kita coba untuk tambahkan owner baru. Sekarang codenya akan terlihat seperti ini:

use std::rc::Rc;

#[derive(Debug)]
struct City {
    name: String,
    population: u32,
    city_history: Rc<String>, // String di dalam Rc
}

#[derive(Debug)]
struct CityData {
    names: Vec<String>,
    histories: Vec<Rc<String>>, // Vec dari Strings yang dibungkus dengan Rc
}

fn main() {
    let calgary = City {
        name: "Calgary".to_string(),
        population: 1_200_000,
           // Anggap saja String city_history ini sangat panjang
        city_history: Rc::new("Calgary began as a fort called Fort Calgary that...".to_string()), // Rc::new() untuk membuat Rc
    };

    let canada_cities = CityData {
        names: vec![calgary.name],
        histories: vec![calgary.city_history.clone()], // .clone() untuk menambah ownernya
    };

    println!("Calgary's history is: {}", calgary.city_history);
    println!("{}", Rc::strong_count(&calgary.city_history));
    let new_owner = calgary.city_history.clone();
}

Hasil cetaknya adalah 2. Dan new_owner bertype Rc<String>. Jika kita menggunakan println!("{}", Rc::strong_count(&calgary.city_history));, maka ownernya sekarang adalah 3.

Apa maksud kata strong dari Rc::strong_count? Pada contoh kasus Rc yang kita lihat di atas, kita sebenarnya membuat sebuah strong pointer. Dan Rc::strong_count itulah yang berguna untuk menghitung banyaknya strong pointer yang ada di suatu program.

Jika ada strong pointer, apakah ada yang dinamakan dengan weak pointer? Jawabannya, ya. Ada yang namanya weak pointer. Weak pointers sangatlah berguna karena jika ada 2 buah Rc yang merujuk satu sama lain, maka keduanya tidak bisa mati. Ini biasa disebut sebagai "reference cycle". Jika item 1 memiliki sebuah Rc ke item 2, dan item 2 memiliki sebuah Rc ke item 1, maka ownernya tidak bisa ke-0 (ownernya tidak bisa berkurang). Pada kasus seperti inilah kita ingin menggunakan weak references. Rc menghitung referencenya, jika ia hanya memiliki weak reference maka ia akan mati (owner = 0). Anda bisa menggunakan Rc::downgrade(&item) sebagai pengganti Rc::clone(&item) untuk membuat weak references. Juga, Anda harus menggunakan Rc::weak_count(&item) untuk melihat banyak weak referencenya.