Cell
と RefCell
はどちらも「内側のミュータビリティ」を実現するものだが、実現方法が違う。
RefCell
については、ランタイムでボローチェックしていることはよく知られている(と思う)。
let ref_cell = RefCell::new(20i32); println!("ref_cell (1): {}", ref_cell.borrow()); println!("ref_cell (2): {}", ref_cell.borrow()); *ref_cell.borrow_mut() = 200; println!("ref_cell (3): {}", ref_cell.borrow());
ref_cell (1): 20 ref_cell (2): 20 ref_cell (3): 200
ランタイムでとは、文字通りコンパイラではなくランタイムで、つまりプログラムで手動で所有権を管理しているということである。
rust/cell.rs at dd4851d503f3fae0c0c742a19e0d8e6e2140bd2a · rust-lang/rust · GitHub
UnsafeCell
というのは、この値は「内側のミュータビリティ」として使うよという意思表示であり、要は生ポインタで処理しますっていうこと。 borrow
というのが借用の数で、正の数の場合は immutable な参照の個数で、負の数の場合は mutable な参照の個数(通常は借用ルールから 1 個まで)と管理されている。
rust/cell.rs at dd4851d503f3fae0c0c742a19e0d8e6e2140bd2a · rust-lang/rust · GitHub
じゃあ、 Cell
はどうかというと、こちらは copy または move によって「内側のミュータビリティ」を実現する。 immutable で定義した値について、 copy または move することによって保持する値を無理矢理変更することができる。
let cell = Cell::new(10i32); println!("cell (1): {}", cell.take()); println!("cell (2): {}", cell.take()); cell.set(100); println!("cell (3): {}", cell.take());
cell (1): 10 cell (2): 0 cell (3): 100
cell (2)
の出力が 0
になっていることに着目したい。これは take()
されることにより、 Cell
の保持する値がデフォルト値に戻ったことを意味する。変数 cell
はイミュータブルで定義されているが、 println!
する度に値が変わっており、きちんと「内側のミュータビリティ」を実現できていることがわかる。
実装は RefCell
より遥かにシンプルなので、興味がある人は読んでみよう。保持している値が Copy
なのか Default
なのかで、2つの異なる API get()
と take()
が実装されているのが面白い。