Chapter 15

内部可変性

📌 内部可変性

Rust には制限付きで,不変オブジェクトを安全に可変にする方法が用意されています.この実装は内部可変性パターン(Interior mutability)と呼ばれるものが使われています.ここでは,詳しく説明しませんが,内部的には unsafe で実装されています.この機能を使うには Cell, RefCell を使います.まずは, Rc について考えながら Cell, RefCell の使い方を見ていきます.

Rc は内部で参照している数を保持することで所有権を共有しています.その数は Rc::strong_count で取得することができます.

use std::rc::Rc;

fn main() {
    let a = Rc::new(10);        // shared reference to immutable object
    dbg!(Rc::strong_count(&a)); // Rc::strong_count(&a) = 1

    let b = Rc::clone(&a);      // cloned shared reference
    dbg!(Rc::strong_count(&a)); // Rc::strong_count(&a) = 2
    dbg!(Rc::strong_count(&b)); // Rc::strong_count(&b) = 2
}

Rc::clone を呼び出すことで,参照カウンタの値が増えていることがわかります.ここで,変数 a は不変にもかかわらず,内部で保持している参照カウンタの値が変わっていることになります.通常は不変束縛の変数から可変参照を作ることができません. Rc::clone は不変参照を引数に取っているので,内部の参照カウンタを変更することが出来ないはずです.

pub fn clone(&self) -> Self

そこで,内部可変性です.不変・可変の検査はコンパイル時に行われますが,内部可変性を使うと実行時(ランタイム時)に行われるようになり,制約が緩くなります.とはいえ,好き勝手に値を変えられては困るので, CellRefCell 型にその機能をまとめて正当性を保つようになっています.ちなみに内部的には UnsafeCell というものを使って実装されています.
Cell の使い方は Cell::new で作成し, get, set で操作します.

use std::cell::Cell;

fn main() {
    let a = Cell::new(10); // immutable object with interior mutability
    dbg!(a.get()); // a.get() = 10
    a.set(20);
    dbg!(a.get()); // a.get() = 20
}

replace は値を置き換えて,以前の値を返します. into_inner は内部の値を取り出します.( T 型に型変換します)

fn main() {
    let a = Cell::new(10); // immutable object with interior mutability
    let b = a.replace(20);
    dbg!(a.get()); // a.get() = 20
    dbg!(b);       // b = 10
    
    let c = a.into_inner(); // turn Cell<T> into T
    dbg!(c);       // c = 20
    dbg!(a);       // borrow check - Error
}

例えば, Rc が内部で保持している参照カウンタを RefCount とした場合, Rc<T> = &(Cell<RefCount>, T) と考えることができます.また, Rc で共有しているオブジェクトを可変にしたい場合,次のようなコードは出来ません.

fn main() {
    let mut a = Rc::new(10); // shared reference to immutable object
    *a = 20; // Error
}

この場合は Cell を使います.

fn main() {
    let a = Rc::new(Cell::new(10));
    a.set(20);
    dbg!(a.get()); // a.get() = 20

    let b = Rc::clone(&a);
    b.set(30);
    dbg!(a.get()); // a.get() = 30
}

Cell<T> の大きな制約として, TCopy トレイト境界があることです.このため,参照版である RefCell<T> があります. RefCell には不変参照を返す borrow, 可変参照を返す borrow_mut メソッドがあり,それぞれ不変参照である Ref 型,可変参照である RefMut 型を返します.

pub fn borrow(&self) -> Ref<'_, T>;
pub fn borrow_mut(&self) -> RefMut<'_, T>;

Ref, RefMut には,通常の不変参照( & ),可変参照( &mut )と同じ制約があります.ただし,この場合の借用チェックは実行時に行われ,その時に制約を満たしていなければ panic! が呼ばれます.

use std::{rc::Rc, cell::RefCell};

fn main() {
    let a = Rc::new(RefCell::new(String::from("hoge")));
    dbg!(a.borrow()); // a.borrow() = "hoge"
    
    *(a.borrow_mut()) = String::from("foo");
    dbg!(a.borrow()); // a.borrow() = "foo"
    
    let b = Rc::clone(&a);
    *(b.borrow_mut()) = String::from("bar");
    dbg!(a.borrow()); // a.borrow() = "bar"
    
    let c = a.borrow_mut();
    let d = a.borrow_mut(); // panic!
}

RefRefMut はRAIIの実装になっていて,複数の可変参照や不変と可変参照が同時に存在することが出来ないように検査しています.

ここまで,所有権を共有する Rc ,内部可変性を持つ CellRefCell を見てきました.
またまた,この図です.

rust-onwership01

Rc, Cell, RefCell を表すと…

rust-cell