このページの目次

Cell, RefCell, Mutex: 内部可変性

Rustの共有参照(&T)はデフォルトで不変です——Cell/RefCell/Mutexは「共有」を前提として、異なるレベルの内部可変性を提供します。CellはCopy型に適しており(実行時オーバーヘッドなし)、RefCellは借用チェックを実行時に行い(パニックでエラー)、Mutexはスレッドセーフなロック(Send)を追加します。

問題の起源

let x = 5;
let r = &x;                          // &i32, 不変
// *r = 6;                           // ERROR: &T は変更できない

Rustの中核的なルール:共有参照(&T)を通じてデータを変更することはできません。これはデータ競合やイテレータの無効化を防ぎます。しかし、⁠論理的には安全な変更がルールによって拒否されることがあります——例えば、計算結果のキャッシュや内部参照カウント。⁠内部可変性は、&self を保持している間、内部状態を変更することを可能にします。

Cell: 値の置き換え(Copy型専用)

use std::cell::Cell;
let c = Cell::new(42);
c.set(100);                          // 内部値を置き換え — &mut self は不要
let val = c.get();                   // コピーして取得

Cell は内部への参照を返しません——get() は常に値をコピーします(T: Copy が必要)、set() は常に値全体を置き換えます。外部に参照が漏れないため、borrow checker はこれを妨げません。適用シーン:単純な i32, bool, enum のラップ。

RefCell: 実行時 Borrow Check

use std::cell::RefCell;
let rc = RefCell::new(vec![1, 2, 3]);
{
    let mut borrow = rc.borrow_mut(); // 実行時: 共有借用がないかチェック
    borrow.push(4);
}                                    // borrow_mut ガードのドロップ → 借用解放
let borrow = rc.borrow();            // 実行時: OK

ルールはコンパイル時の borrow checker と同じ——エイリアシング XOR 変更——ですが、チェックは実行時に行われます。違反 → panic!(コンパイルエラーではありません)。borrow()Ref<T>&T への Deref)を返し、borrow_mut()RefMut<T>&mut T への Deref)を返します。これらのガードはスコープを抜ける際にドロップされ、内部の借用カウントを解放します。

RefCell は Sync ではありません——スレッド間で共有できません。

Mutex: 内部可変性 + Send + ロック

use std::sync::Mutex;
let m = Mutex::new(42);
let mut guard = m.lock().unwrap();   // ロックを取得するまでブロック
*guard += 1;                         // DerefMut を通じて内部値にアクセス
drop(guard);                         // ロック解放 (スコープ終了に依存も可)

MutexRefCell の内部可変性 + OSロック + Send 保証を組み合わせます。内部実装は UnsafeCell + OS mutex(Linux では pthread_mutex_t)です。lock() はロックを取得するまで現在のスレッドをブロックし、try_lock() はノンブロッキングで、すぐに Option<MutexGuard> を返します。

三者比較

CellRefCellMutex
内部可変性YYY
参照の返却N (コピーのみ)Y (Ref/RefMut)Y (MutexGuard)
競合処理N/Apanicblock
スレッドセーフNNY
チェック時期コンパイル時 (借用なし)実行時 (panic)実行時 (block)

参考

  • Rust Book: Chapter 15.5
  • Rustonomicon: UnsafeCell (すべての内部可変性の背後にあるプリミティブ)

Keywords: Cell, RefCell, Mutex, interior mutability, UnsafeCell, borrow_mut, runtime borrow check