Rust的所有權機制,要求一個資源同一時刻有且只能有一個擁有所有權的綁定或&mut引用,目的為保證內存的安全。在大多數情況下,都沒有問題,但是考慮以下情況:
- 在圖數據結構中,多個邊可能會擁有同一個節點,該節點直到沒有邊指向它時,才應該被釋放清理。
- 在多線程中,多個線程可能會持有同一個數據,但是你受限於Rust的安全機制,無法同時獲取此數據的可變引用。
為了解決此類問題,Rust在所有權機制之外又引入了額外的措施來簡化相應的實現:通過引用計數的方式,允許一個數據資源在同一時刻擁有多個所有者。
這種實現機制就是Rc和Arc,前者適用於單線程,后者適用於多線程。
Rc
引用計數(reference counting),顧名思義,通過記錄一個數據被引用的次數來確定該數據是否正在被使用。當引用次數歸零時,就代表該數據不再被使用,因此可以被清理釋放。
而Rc正是引用計數的英文縮寫。當我們希望在堆上分配一個對象供程序的多個部分使用且無法確定哪個部分最后一個結束時,就可以使用Rc成為數據據值的所有者。
以下是經典的所有權被轉移導致報錯的例子:
let s = String::from("hello"); //s在這里轉移給a let a = Box::new(s); //報錯。這里繼續嘗試把s轉移給b let b = Box::new(s);
error[E0382]: use of moved value: `s` --> src/main.rs:33:22 | 29 | let s = String::from("hello"); | - move occurs because `s` has type `String`, which does not implement the `Copy` trait 30 | //s在這里轉移給a 31 | let a = Box::new(s); | - value moved here 32 | //報錯。這里繼續嘗試把s轉移給b 33 | let b = Box::new(s); | ^ value used here after move
使用Rc就可以解決:
let s = Rc::new(String::from("hello")); let b = Rc::clone(&s);
以上代碼我們使用Rc::new創建了一個新的Rc<String>智能指針並賦給變量s,該指針指向底層的字符串數據。
智能指針Rc<T>在創建時,還會將此用計數加1,此時獲取引用計數的關聯函數Rc::strong_count返回的值將是1。
Rc::clone
接着,我們又使用Rc::clone克隆了一份智能指針Rc<String>,並將該智能指針的引用計數增加到2。
不要被clone所迷惑,以為所有的clone都是深拷貝。這里的clone僅僅復制了智能指針並增加了引用計數,並沒有克隆底層數,因為a和b是共享了底層的字符串s,這種復制效率是非常高的。當然我們也可以使用s.clone()的方式來克隆,但是從可讀性角度,我們更加推薦Rc::clone的方式。
Arc
Arc是 Atomic Rc 的縮寫,顧名思義:原子化的Rc<T>智能指針。原子化是一種並發原語,我們在后續章節會進行深入講解,這里你只要知道它能保證我們的數據能夠安全的在線程間共享即可。
Arc 的性能損耗
你可能好奇,為何不直接使用Arc,還要畫蛇添足弄一個Rc,還有 Rust 的基本數據類型、標准庫數據類型為什么不自動實現原子化操作?這樣就不存在線程不安全的問題了。
原因在於原子化或者其它鎖雖然可以帶來的線程安全,但是都會伴隨着性能損耗,而且這種性能損耗還不小。因此 Rust 把這種選擇權交給你,畢竟需要線程安全的代碼其實占比並不高,大部分時候我們開發的程序都在一個線程內。