本文へスキップ
BecomeCoder

Rustコース · 第10章 スマートポインタと並行性 · レッスン41

RefCell と内部可変性

ブラウザで完結

導入

第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 でパニックします。「借用ルールは消えず、チェックが実行時に移るだけ」という点を確認しましょう。多用するものではなく、必要なときの道具です。

実際に動かしてみよう

下のエディタにRustを書いて「実行」を押すと、学習用シミュレータが println! の出力を表示します(本物のrustcではなく、教材の範囲を再現した軽量エンジンです)。struct・enum・match・トレイト・Vec/String/HashMap・Option/Result なども動きます。本文の例を書き換えて動かしてみましょう(所有権・借用チェッカーのコンパイルエラーは再現しないため、動きの確認用として使ってください)。

Rust — ブラウザ内で実行(学習用シミュレータ)

Rustの教材サブセットを動かす学習用シミュレータを読み込みます(本物のrustcではなく、動きを再現した軽量な自作エンジンです)。
スクロールして表示された時点でも自動で読み込まれます。