內部可變性 (Interior Mutability)

在某些情況下,您必須修改共用 (唯讀) 參照背後的資料:比方說,共用的資料結構可能含有內部快取,並想透過唯讀方法更新該快取。

「內部可變動性」模式可以在共用參照背後提供專屬 (可變動的) 存取權。標準程式庫支援以多種方式執行此操作,同時仍可確保安全,做法通常是執行執行階段檢查。

RefCell

use std::cell::RefCell;
use std::rc::Rc;

#[derive(Debug, Default)]
struct Node {
    value: i64,
    children: Vec<Rc<RefCell<Node>>>,
}

impl Node {
    fn new(value: i64) -> Rc<RefCell<Node>> {
        Rc::new(RefCell::new(Node { value, ..Node::default() }))
    }

    fn sum(&self) -> i64 {
        self.value + self.children.iter().map(|c| c.borrow().sum()).sum::<i64>()
    }
}

fn main() {
    let root = Node::new(1);
    root.borrow_mut().children.push(Node::new(5));
    let subtree = Node::new(10);
    subtree.borrow_mut().children.push(Node::new(11));
    subtree.borrow_mut().children.push(Node::new(12));
    root.borrow_mut().children.push(subtree);

    println!("graph: {root:#?}");
    println!("graph sum: {}", root.borrow().sum());
}

Cell

Cell 會納入值,並允許取得或設定該值,即使具有對 Cell 的共用參照也一樣。但是,它不允許對該值進行任何參照。由於沒有參照,因此借用規則不得違反。

This slide should take about 10 minutes.

這張投影片的重點是 Rust 提供「安全的」方法,可讓您修改共用參照背後的資料。要確保安全性有許多方式,而 RefCellCell 是其中兩種方法。

  • RefCell 會透過執行階段檢查,強制使用 Rust 的一般借用規則 (多個共用參照或單一專屬參照)。在本例中,所有借用都非常短暫且永遠不會重疊,因此檢查一律會成功。

  • Rc 只允許對自身內容的共用 (唯讀) 存取行為,因為允許 (並計算) 多個參照才是它的用途。但是,由於我們要修改這個值,因此內部可變動性不可或缺。

  • 如要確保安全,Cell 是較簡單的做法,因為其中的 set 方法可接受 &self。這無需動用執行階段檢查,但需要移動值,因此可能有其相應成本。

  • Demonstrate that reference loops can be created by adding root to subtree.children.

  • 如要演示執行階段發生的恐慌情形,請新增 fn inc(&mut self),這可讓 self.value 遞增,並在其子項呼叫相同的方法。在有參照迴圈的情況下,這會引發恐慌,其中的 thread 'main' 會因 'already borrowed: BorrowMutError' 而恐慌。