📌 内部可変性
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
そこで,内部可変性です.不変・可変の検査はコンパイル時に行われますが,内部可変性を使うと実行時(ランタイム時)に行われるようになり,制約が緩くなります.とはいえ,好き勝手に値を変えられては困るので, Cell
や RefCell
型にその機能をまとめて正当性を保つようになっています.ちなみに内部的には 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>
の大きな制約として, T
は Copy
トレイト境界があることです.このため,参照版である 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!
}
Ref
, RefMut
はRAIIの実装になっていて,複数の可変参照や不変と可変参照が同時に存在することが出来ないように検査しています.
ここまで,所有権を共有する Rc
,内部可変性を持つ Cell
と RefCell
を見てきました.
またまた,この図です.
Rc
, Cell
, RefCell
を表すと…