1. 為什麼需要 Rc
在 Rust 的所有權規則中,一個值同一時間只能有一個擁有者。但有時候,我們希望能讓多個變數共同擁有一份資料,像多個節點同時指向同一段記憶體的情況,這時候就需要 Rc(Reference Counted 智慧指標)。
Rc 透過「引用計數」來追蹤資料被多少個擁有者使用,當最後一個擁有者離開作用域時,記憶體才會被釋放。但 Rc 僅能在單執行緒(single-thread)環境下使用,如果是多執行緒就要用 Arc。
2. Rc 的基本使用
use std::rc::Rc;
fn main() {
let a = Rc::new(String::from("Rust"));
let b = Rc::clone(&a); // 增加引用計數
let c = Rc::clone(&a);
println!("a = {}", a);
println!("引用數量 = {}", Rc::strong_count(&a));
}
輸出:
a = Rust
引用數量 = 3
這裡 Rc::clone(&a)不會複製資料,只會讓引用計數加一,當 a、b、c 全部離開作用域後,Rust 會自動釋放那份記憶體。
3. Rc 在結構中的應用
假設要建立一個共享節點的鏈結結構(linked list),可以用 Rc 讓多個節點共用同一個尾端。
use std::rc::Rc;
enum List {
Cons(i32, Rc<List>),
Nil,
}
use List::{Cons, Nil};
fn main() {
let a = Rc::new(Cons(5, Rc::new(Cons(10, Rc::new(Nil)))));
println!("count after creating a = {}", Rc::strong_count(&a));
let b = Cons(3, Rc::clone(&a));
println!("count after creating b = {}", Rc::strong_count(&a));
{
let c = Cons(4, Rc::clone(&a));
println!("count after creating c = {}", Rc::strong_count(&a));
}
println!("count after c goes out of scope = {}", Rc::strong_count(&a));
}
輸出:
count after creating a = 1
count after creating b = 2
count after creating c = 3
count after c goes out of scope = 2
可以看到每當建立新的 Rc 引用,計數就加一,當變數離開作用域時,計數就減一,當計數歸零時,Rc 內的資料就會被釋放。
4. Rc::clone vs 常規 clone
Rc::clone() 不會 複製底層資料,只會增加引用計數,相反地,對一般型別呼叫 .clone() 會建立一份新的獨立資料,因此:
let x = Rc::new(String::from("Rust"));
let y = Rc::clone(&x); // 共用資料
let z = (*x).clone(); // 產生新字串
這樣 y 和 x 會共用同一個 heap 資料,而 z 則是完全新的複本。
5. Rc 的限制
Rc 不支援可變借用(&mut),因為這樣會導致多重擁有者同時修改資料而破壞安全性,如果需要在多重擁有權下修改資料,要搭配 RefCell 使用。
6. 學習心得與補充
學到 Rc 之後,我對 Rust 的所有權模型有了更深的理解。原本我以為一個值只能有一個擁有者的規則會讓設計變得很受限,但 Rc 讓我看到 Rust 是如何在保持安全的前提下,實現共享資料的可能。用 Rc 的時候,感覺就像在和編譯器合作,我可以有多個變數指向同一份資料,但同時又不需要擔心誰該釋放記憶體,雖然 Rc 無法修改共享資料,但我覺得這其實是一種刻意的限制,為的是讓我清楚知道什麼時候安全。我體會到Rust 所謂安全的共享到底是什麼意思,也更期待明天學到能結合 Rc 使用的 RefCell。