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,另一個叫 CityData。City 有關於一個城市的資訊,而 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() {}
用 Rc 把 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() {}
要增加新的參考,你必須克隆 Rc。但是等一下,我們不是想避免使用 .clone() 嗎?不完全是:我們只是不想克隆整個 String。但是 Rc 的克隆只是克隆了指標(pointer)--它基本上是沒有開銷的。這就像在一盒書上貼上名字貼紙,證明有兩個人擁有它,而不是做一盒全新的書。
你可以用 item.clone() 或者用 Rc::clone(&item) 來克隆叫做 item 的 Rc。所以 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) 來檢視弱參考的數量。