RefCell

std::marker::Copy未実装な型でも扱えるCell<T>(内側のTが可変)です。

RefCellでは::borrowで借用、::borrow_mutでミュータブル借用が行えます。つまりCell::setにあたるものは無く、ミュータブル借用で取得した値を直接更新します。

::borrowの戻り値はRef<T>となりますが、これはDerefが実装されている為Tに変換可能です。

以下はコンパイルできる適当なコードです。

use std::cell::RefCell;

let ref_cell_string: RefCell<String> = RefCell::new("foo".into());
let ref_ref_cell_string = &ref_cell_string;

*ref_ref_cell_string.borrow_mut() += "bar";

{
  let ref_ref_cell_string_in_scope = &ref_cell_string;
  *ref_ref_cell_string_in_scope.borrow_mut() += "baz";
}

assert_eq!(*ref_cell_string.borrow(), "foobarbaz".to_string());

以下はコンパイルできない Panic を起こすコードです。違いは::borrow_mutを一旦変数に置いている点だけです。

use std::cell::RefCell;

let ref_cell_string: RefCell<String> = RefCell::new("foo".into());
let ref_ref_cell_string = &ref_cell_string;

let mut _borrowed = ref_ref_cell_string.borrow_mut();
*_borrowed += "bar";

{
  // snap
}

assert_eq!(*ref_cell_string.borrow(), "foobarbaz".to_string());

これはRefCell::borrow::borrow_mutによる借用にも Rust の基本的な借用ルールが適用される為です。それは、

  1. イミュータブル参照であればいくらでも大丈夫
  2. ミュータブル参照は1つだけにしてね

というようなものでした。

上記の場合_borrowedがミュータブル参照としてスコープにある状態で、ref_cell_string.borrow()によるイミュータブル参照を取ろうとした為 Panic してしまいました。

直すには借用ルールと同じようにミュータブル参照を扱う部分を小さなスコープ化に閉じ込めてしまえば大丈夫です。

use std::cell::RefCell;

let ref_cell_string: RefCell<String> = RefCell::new("foo".into());
let ref_ref_cell_string = &ref_cell_string;

{
  let mut _borrowed = ref_ref_cell_string.borrow_mut();
  *_borrowed += "bar";
}

{
  let ref_ref_cell_string_in_scope = &ref_cell_string;
  let mut _borrowed = ref_ref_cell_string_in_scope.borrow_mut();
  *_borrowed += "baz";
}

assert_eq!(*ref_cell_string.borrow(), "foobarbaz".to_string());

先程 Panic が起きてしまったと言ったようにRefCellはコンパイル時にルールが守られているか確認していません。全ては実行時に初めて確認されています。