内部可変性

場合によっては、共有(読み取り専用)参照の背後にあるデータを変更する必要があります。たとえば、共有データ構造に内部キャッシュがあり、そのキャッシュを読み取り専用メソッドから更新する必要がある場合があります。

「内部可変性」パターンは、共有参照を通した排他的(可変)アクセスを可能にします。標準ライブラリには、これを安全に行うための方法がいくつか用意されており、通常はランタイム チェックを実行することで安全性を確保します。

Cell

Cell wraps a value and allows getting or setting the value using only a shared reference to the Cell. However, it does not allow any references to the inner value. Since there are no references, borrowing rules cannot be broken.

use std::cell::Cell;

fn main() {
    // Note that `cell` is NOT declared as mutable.
    let cell = Cell::new(5);

    cell.set(123);
    println!("{}", cell.get());
}

RefCell

RefCell allows accessing and mutating a wrapped value by providing alternative types Ref and RefMut that emulate &T/&mut T without actually being Rust references.

These types perform dynamic checks using a counter in the RefCell to prevent existence of a RefMut alongside another Ref/RefMut.

By implementing Deref (and DerefMut for RefMut), these types allow calling methods on the inner value without allowing references to escape.

use std::cell::RefCell;

fn main() {
    // Note that `cell` is NOT declared as mutable.
    let cell = RefCell::new(5);

    {
        let mut cell_ref = cell.borrow_mut();
        *cell_ref = 123;

        // This triggers an error at runtime.
        // let other = cell.borrow();
        // println!("{}", *other);
    }

    println!("{cell:?}");
}
This slide should take about 10 minutes.

このスライドで重要なのは、Rust には、共有参照の背後にあるデータを変更する安全な方法が用意されているということです。安全性を確保するにはさまざまな方法がありますが、ここでは RefCellCell を取り上げます。

  • RefCell は、ランタイム チェックとともに Rust の通常の借用ルール(複数の共有参照または単一の排他参照)を適用します。この場合、すべての借用は非常に短く、重複しないため、チェックは常に成功します。

    • The extra block in the RefCell example is to end the borrow created by the call to borrow_mut before we print the cell. Trying to print a borrowed RefCell just shows the message "{borrowed}".
  • Cell は安全性を確保するためのよりシンプルな手段であり、&self を受け取る set メソッドを備えています。ランタイム チェックは必要ありませんが、値を移動する必要があり、それによってコストが発生することがあります。

  • Both RefCell and Cell are !Sync, which means &RefCell and &Cell can't be passed between threads. This prevents two threads trying to access the cell at once.