Rc

Rc 的意思是 "參考計數器(reference counter)"。你知道在 Rust 中,每個變數只能有一個所有者(owner)。這就是為什麼這個不能執行的原因:

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); // ⚠️
}

takes_a_string 拿走 user_name 之後,你就不能再用它了。這樣也沒問題:你可以直接給它 user_name.clone()。但有時變數是某個結構體的一部分,也許你不能克隆這個結構。或者也許 String 真的很長,你不想克隆它。這些都是會有 Rc 的一些原因,它讓你可以有多個所有者。Rc 就像個優秀的辦公人員:Rc 寫下誰擁有所有權,以及有多少個。然後一旦所有者的數量下降到 0,這個變數就可以消失不要了。

這裡告訴你如何使用 Rc。首先想像兩個結構體:一個叫 City,另一個叫 CityDataCity 有關於一個城市的資訊,而 CityData 把所有的城市都一起放在 Vec 中。

#[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,
           // 假裝這個字串非常非常長
        city_history: "Calgary began as a fort called Fort Calgary that...".to_string(),
    };

    let canada_cities = CityData {
        names: vec![calgary.name], // 用 calgary.name 比較短
        histories: vec![calgary.city_history], // 但這個字串非常長
    };

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

當然這是不可能執行的,因為現在 canada_cities 擁有了資料,而 calgary 沒有。它說:

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

我們可以克隆名稱:names: vec![calgary.name.clone()],但是我們不想克隆很長的 city_history。所以我們可以用 Rc

加上 use 的宣告:

use std::rc::Rc;

fn main() {}

RcString 包起來:

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() {}

要增加新的參考,你必須克隆 Rc。但是等一下,我們不是想避免使用 .clone() 嗎?不完全是:我們只是不想克隆整個 String。但是 Rc 的克隆只是克隆了指標(pointer)--它基本上是沒有開銷的。這就像在一盒書上貼上名字貼紙,證明有兩個人擁有它,而不是做一盒全新的書。

你可以用 item.clone() 或者用 Rc::clone(&item) 來克隆叫做 itemRc。所以 calgary.city_history 有兩個所有者。我們可以用 Rc::strong_count(&item) 查詢所有者的數量。另外我們再增加一個新的所有者。現在我們的程式碼看起來像這樣:

use std::rc::Rc;

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

#[derive(Debug)]
struct CityData {
    names: Vec<String>,
    histories: Vec<Rc<String>>, // 有包在 Rc 裡的 String 的向量
}

fn main() {
    let calgary = City {
        name: "Calgary".to_string(),
        population: 1_200_000,
           // 假裝這個字串非常非常長
        city_history: Rc::new("Calgary began as a fort called Fort Calgary that...".to_string()), // 用 Rc::new() 做出 Rc
    };

    let canada_cities = CityData {
        names: vec![calgary.name],
        histories: vec![calgary.city_history.clone()], // 用 .clone() 來增加計數
    };

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

印出 2。而 new_owner 現在是 Rc<String>。現在如果我們用 println!("{}", Rc::strong_count(&calgary.city_history));,我們得到 3

那麼,如果有強指標,是否有弱指標(weak references)呢?是的,有。弱指標蠻有用的,因為如果有兩個 Rc 互相指向對方,它們就不會死掉。這就是所謂的"循環參考(reference cycle)"。如果第 1 項有 Rc 指向第 2 項,而第 2 項有 Rc 指向第 1 項,計數就不會降到 0,在這種情況下,你會想要使用弱參考。那麼 Rc 就會對參考計數,但如果只有弱參考它就可以死掉。你要使用 Rc::downgrade(&item) 而不是 Rc::clone(&item) 來做出弱參考。另外,你需要用 Rc::weak_count(&item) 來檢視弱參考的數量。