本页目录

Cell, RefCell, Mutex: 内部可变性

Rust 的共享引用(&T)默认不可变——Cell/RefCell/Mutex 在"共享"的前提下提供了不同程度的内部可变性。Cell 适合 Copy 类型(无运行时开销),RefCell 把借用检查推迟到运行时(panic 报错),Mutex 加上线程安全的锁(Send)。

问题的起源

let x = 5;
let r = &x;                          // &i32, 不可变
// *r = 6;                           // ERROR: &T 不能被修改

Rust 的核心规则:通过共享引用(&T)不能修改数据。这防止了数据竞争和 iterator invalidation。但有时逻辑上是安全的修改却被规则拒绝了⁠——比如缓存计算结果、内部引用计数。⁠内部可变性允许你在拥有 &self 时修改内部状态。

Cell: 替换整个值(Copy 类型专用)

use std::cell::Cell;
let c = Cell::new(42);
c.set(100);                          // 替换内部值 — 不需要 &mut self
let val = c.get();                   // Copy 出来

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(); // runtime: 检查无 shared borrow
    borrow.push(4);
}                                    // borrow_mut guard drop → 释放 borrow
let borrow = rc.borrow();            // runtime: OK

规则与编译期 borrow checker 相同——aliasing XOR mutation——但检查发生在运行时⁠。违反 → panic!(不是编译错误)。borrow() 返回 Ref<T>(Deref to &T),borrow_mut() 返回 RefMut<T>(Deref to &mut T)。这些 guard 在离开作用域时 drop,释放内部 borrow count。

RefCell 不是 Sync——不能跨线程共享。

Mutex: 内部可变性 + Send + 锁

use std::sync::Mutex;
let m = Mutex::new(42);
let mut guard = m.lock().unwrap();   // 阻塞直到获得锁
*guard += 1;                         // 通过 DerefMut 访问内部值
drop(guard);                         // 释放锁 (也可依赖 scope exit)

Mutex 结合了 RefCell 的内部可变性 + OS 锁 + Send 保证。底层是 UnsafeCell + OS mutex(pthread_mutex_t on Linux)。lock() 阻塞当前线程直到获得锁,try_lock() 非阻塞,立即返回 Option<MutexGuard>

三者对比

CellRefCellMutex
内部可变性YYY
返回引用N (只 copy)Y (Ref/RefMut)Y (MutexGuard)
冲突处理N/Apanicblock
Thread-safeNNY
检查时机编译期 (no borrow)运行时 (panic)运行时 (block)

参考

  • Rust Book: Chapter 15.5
  • Rustonomicon: UnsafeCell (the primitive behind all interior mutability)

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