深入研究 Rust 的內部可變性 - Cell 是如何工作的?
在 rust 中,我們從引用借用的規則中知道有不可變 (共享) 引用和可變 (獨佔) 引用。
如果我們有一個共享引用,我們可以想要多少就有多少。這是因爲這些引用不允許我們改變它們指向的值,所以同時有多個引用是可以的。
可變引用則不然,顧名思義,可變引用允許我們改變它們所指向的值。所以在這種情況下,對值有多個引用是不行的。例如,考慮兩個線程,其中每個線程都持有一個獨佔引用並同時更改其值。線程運行後的值應該是什麼?確切地說,這是一個未定義的行爲!
那麼爲什麼 rust 允許我們擁有可共享的可變容器呢?這不是打破了 Rust 的借用規則嗎?這是因爲這些容器有限制,允許以安全的方式使用它們,同時仍然提供允許可變的 api。這就是爲什麼這些類型提供 “內部可變性”,當使用它們時,它們作爲容器施加限制,在這些限制下,它們所持有的類型可以被安全地修改!
Cell 如何提供內部可變性?
基本上,Cell 通過確保沒有指向其保存的數據的指針並且在單線程環境中執行來實現這一點。
有了這些限制,更改 Cell 中的數據是完全可以的。想想看,如果我們知道 Cell 中沒有指向數據的指針,並且它不是跨線程共享的,則可以保證我們對它具有獨佔訪問權。
現在的問題是,Cell 是如何施加這些約束的?Cell 通過從不返回對其內部數據的引用來實現這一點,它總是返回數據的副本。因此,這已經告訴 Cell 適用於內存開銷小的類型,例如整數。
此外,Cell 沒有實現 Sync,因此它不能在線程邊界之間共享。
Cell 的構建塊是 UnsafeCell,這是 Rust 內部可變性的構建塊之一。UnsafeCell 允許我們在任何時候獲得一個原始的獨佔指針,指向它所保存的數據。這當然是一個不安全的操作,所以我們必須在 unsafe{} 塊中進行操作。
Cell 的一種可能的簡化實現是:
use std::cell::UnsafeCell;
struct Cell<T> {
value: UnsafeCell<T>
}
// 禁止跨線程使用Cell
impl<T> !Sync for Cell<T> {}
impl<T> Cell<T> {
pub fn new(value: T) -> Self {
Cell { value: UnsafeCell::new(value) }
}
pub fn set(self, value: T) {
// 用一個新值覆蓋單元格所指向的值
unsafe { *self.value.get() = value }
}
pub fn get(&self) -> T where T: Copy {
// 返回Cell所指向的數據的副本
unsafe { *self.value.get() }
}
}
這裏我們使用 UnsafeCell 來存儲 Cell 的數據,不允許在線程之間共享此類型,最後,我們從不引用 Cell 中的數據。注意,get 方法只適用於實現 Copy 的類型,並且返回內部類型的副本。
在本文中,我們探討了 Rust 的 Cell 類型,我們瞭解到 Cell 通過對其持有的數據施加約束來允許內部可變性。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/PY9MKzsOd024PDNC8ZNOpQ