Rc 和 Arc
Rust 建立在所有權之上的這一套機制,它要求一個資源同一時刻有且只能有一個擁有所有權的綁定或 &mut
引用,這在大部分的情況下保證了內存的安全。但是這樣的設計是相當嚴格的,在另外一些情況下,它限制了程序的書寫,無法實現某些功能。因此,Rust 在 std 庫中提供了額外的措施來補充所有權機制,以應對更廣泛的場景。
默認 Rust 中,對一個資源,同一時刻,有且只有一個所有權擁有者。Rc
和 Arc
使用引用計數的方法,讓程序在同一時刻,實現同一資源的多個所有權擁有者,多個擁有者共享資源。
Rc
Rc
用於同一線程內部,通過 use std::rc::Rc
來引入。它有以下幾個特點:
- 用
Rc
包裝起來的類型對象,是immutable
的,即 不可變的。即你無法修改Rc<T>
中的T
對象,只能讀; - 一旦最後一個擁有者消失,則資源會被自動回收,這個生命週期是在編譯期就確定下來的;
Rc
只能用於同一線程內部,不能用於線程之間的對象共享(不能跨線程傳遞);Rc
實際上是一個指針,它不影響包裹對象的方法調用形式(即不存在先解開包裹再調用值這一說)。
例子:
use std::rc::Rc;
let five = Rc::new(5);
let five2 = five.clone();
let five3 = five.clone();
Rc Weak
Weak
通過 use std::rc::Weak
來引入。
Rc
是一個引用計數指針,而 Weak
是一個指針,但不增加引用計數,是 Rc
的 weak 版。它有以下幾個特點:
- 可訪問,但不擁有。不增加引用計數,因此,不會對資源回收管理造成影響;
- 可由
Rc<T>
調用downgrade
方法而轉換成Weak<T>
; Weak<T>
可以使用upgrade
方法轉換成Option<Rc<T>>
,如果資源已經被釋放,則 Option 值為None
;- 常用於解決循環引用的問題。
例子:
use std::rc::Rc;
let five = Rc::new(5);
let weak_five = Rc::downgrade(&five);
let strong_five: Option<Rc<_>> = weak_five.upgrade();
Arc
Arc
是原子引用計數,是 Rc
的多線程版本。Arc
通過 std::sync::Arc
引入。
它的特點:
Arc
可跨線程傳遞,用於跨線程共享一個對象;- 用
Arc
包裹起來的類型對象,對可變性沒有要求; - 一旦最後一個擁有者消失,則資源會被自動回收,這個生命週期是在編譯期就確定下來的;
Arc
實際上是一個指針,它不影響包裹對象的方法調用形式(即不存在先解開包裹再調用值這一說);Arc
對於多線程的共享狀態幾乎是必須的(減少複製,提高性能)。
示例:
use std::sync::Arc;
use std::thread;
fn main() {
let numbers: Vec<_> = (0..100u32).collect();
let shared_numbers = Arc::new(numbers);
for _ in 0..10 {
let child_numbers = shared_numbers.clone();
thread::spawn(move || {
let local_numbers = &child_numbers[..];
// Work with the local numbers
});
}
}
Arc Weak
與 Rc
類似,Arc
也有一個對應的 Weak
類型,從 std::sync::Weak
引入。
意義與用法與 Rc Weak
基本一致,不同的點是這是多線程的版本。故不再贅述。
一個例子
下面這個例子,表述的是如何實現多個對象同時引用另外一個對象。
use std::rc::Rc;
struct Owner {
name: String
}
struct Gadget {
id: i32,
owner: Rc<Owner>
}
fn main() {
// Create a reference counted Owner.
let gadget_owner : Rc<Owner> = Rc::new(
Owner { name: String::from("Gadget Man") }
);
// Create Gadgets belonging to gadget_owner. To increment the reference
// count we clone the `Rc<T>` object.
let gadget1 = Gadget { id: 1, owner: gadget_owner.clone() };
let gadget2 = Gadget { id: 2, owner: gadget_owner.clone() };
drop(gadget_owner);
// Despite dropping gadget_owner, we're still able to print out the name
// of the Owner of the Gadgets. This is because we've only dropped the
// reference count object, not the Owner it wraps. As long as there are
// other `Rc<T>` objects pointing at the same Owner, it will remain
// allocated. Notice that the `Rc<T>` wrapper around Gadget.owner gets
// automatically dereferenced for us.
println!("Gadget {} owned by {}", gadget1.id, gadget1.owner.name);
println!("Gadget {} owned by {}", gadget2.id, gadget2.owner.name);
// At the end of the method, gadget1 and gadget2 get destroyed, and with
// them the last counted references to our Owner. Gadget Man now gets
// destroyed as well.
}