導入
第5章の借用ルールは「可変参照は同時に1つ、不変参照と同時には持てない」でした。これはコンパイル時にチェックされます。ところが、このルールをコンパイル時には通しつつ、実行時にチェックしたい場面があります。それを可能にするのが RefCell<T> と「内部可変性」という考え方です。
説明
RefCell<T> は、「不変に見える値の中身を、実行時のチェック付きで書き換えられる」箱です。これを 内部可変性(interior mutability) と呼びます。
use std::cell::RefCell;
fn main() {
let data = RefCell::new(5); // data 自体は mut ではない
*data.borrow_mut() += 10; // 実行時に「可変借用」して書き換え
println!("{}", data.borrow()); // 15(不変借用で読む)
}
borrow_mut()… 可変借用を実行時に取得(他に借用がなければOK)。borrow()… 不変借用を取得。- 借用ルール自体は変わりません。変わるのはチェックのタイミングです。コンパイル時ではなく実行時に検査し、もしルール違反(たとえば可変借用が2つ同時)が起きると、コンパイルエラーではなく実行時パニックになります。
RefCell の真価は、Rc と組み合わせたときに出ます。Rc は「複数の所有者」を、RefCell は「共有しながら書き換え」を担当し、Rc<RefCell<T>> で「複数の場所から共有され、かつ変更できるデータ」が作れます。
use std::rc::Rc;
use std::cell::RefCell;
fn main() {
let shared = Rc::new(RefCell::new(vec![1, 2, 3]));
let clone1 = Rc::clone(&shared);
clone1.borrow_mut().push(4); // 共有先から中身を書き換え
// shared からも変更が見える(同じデータを共有しているから)
println!("{:?}", shared.borrow()); // [1, 2, 3, 4]
}
Rc<RefCell<T>>… 「共有所有(Rc)」+「内部可変性(RefCell)」の定番の組み合わせ。木構造でノードを共有しつつ更新する、といった場面で使います。
なぜ Rust はコンパイル時チェックを基本にしながら、RefCell という実行時チェックの逃げ道を用意しているのか。それは、借用チェッカーが安全なのに「安全だと証明できない」ケースを保守的に弾いてしまうことがあるからです。「自分は安全だと分かっている、責任は持つ」というときに、実行時チェックへ切り替える手段が RefCell です。安全性は保ちつつ(違反すればパニックで止まる)、表現力を広げる仕組みです。
試すには
RefCell の中身を同時に2つ borrow_mut() すると、コンパイルは通っても実行時に already borrowed でパニックします。「借用ルールは消えず、チェックが実行時に移るだけ」という点を確認しましょう。多用するものではなく、必要なときの道具です。