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)
來檢視弱參考的數量。