Skip to content

Ch 6: std::cell - 内部可变性

内部可变性(Interior Mutability)是 Rust 中一种设计模式,允许在持有不可变引用的同时修改数据。本章将详细讲解 Cell、RefCell、UnsafeCell 和 CoerceUnsized 四种核心类型,理解它们的工作原理和使用场景。

在 Rust 中,通常的规则是:要么拥有不可变数据(&T),要么拥有可变数据(&mut T)。内部可变性打破了这个规则:

// 普通情况:&T 不能修改数据
let x = 5;
let r = &x;
// *r = 10; // 编译错误!
// 内部可变性:即使持有 &T 也可以修改
use std::cell::Cell;
let x = Cell::new(5);
let r = &x;
r.set(10); // OK!
println!("{}", x.get()); // 10

Cell<T> 提供了一种简单的内部可变性,适用于实现了 Copy 的类型:

特点:

  • get() 方法获取值的拷贝
  • set() 方法设置新值
  • 替代方案:replace() 替换值并返回旧值
  • 替代方案:take() 取出值并放默认值

示例:

use std::cell::Cell;
fn main() {
let cell = Cell::new(5);
// get - 获取值的拷贝
println!("{}", cell.get()); // 5
// set - 设置新值
cell.set(10);
println!("{}", cell.get()); // 10
// replace - 替换并返回旧值
let old = cell.replace(20);
println!("old = {}, new = {}", old, cell.get()); // old = 10, new = 20
// take - 取出值,放默认值
let taken = cell.take();
println!("taken = {}, now = {:?}", taken, cell.get()); // taken = 20, now = 0
}

Cell 的限制: 只能用于 Copy 类型:

use std::cell::Cell;
// Cell<String> 无法编译,因为 String 不是 Copy
// let cell = Cell::new(String::from("hello"));
// cell.set(String::from("world")); // 错误!

4. RefCell - 引用计数的内部可变性

Section titled “4. RefCell - 引用计数的内部可变性”

RefCell<T> 用于提供内部可变性,适用于非 Copy 类型:

特点:

  • 运行时借用检查
  • borrow() 返回 Ref<T>
  • borrow_mut() 返回 RefMut<T>
  • 借用违反规则时 panic

示例:

use std::cell::RefCell;
fn main() {
let data = RefCell::new(vec![1, 2, 3]);
// borrow - 不可变借用
let v = data.borrow();
println!("{:?}", v); // [1, 2, 3]
// borrow_mut - 可变借用
data.borrow_mut().push(4);
// 再次借用
let v = data.borrow();
println!("{:?}", v); // [1, 2, 3, 4]
}

借用规则违反时 panic:

use std::cell::RefCell;
fn main() {
let data = RefCell::new(5);
let r1 = data.borrow();
let r2 = data.borrow(); // 两个不可变借用,OK
println!("r1 = {}, r2 = {}", r1, r2);
// 不可变借用仍在作用域内
// let r3 = data.borrow_mut(); // panic!同时有不可变和可变借用
}

RefCell 的典型用法:

use std::cell::RefCell;
struct Counter {
value: RefCell<i32>,
}
impl Counter {
fn new() -> Self {
Counter { value: RefCell::new(0) }
}
fn increment(&self) {
*self.value.borrow_mut() += 1;
}
fn get(&self) -> i32 {
*self.value.borrow()
}
}
fn main() {
let counter = Counter::new();
counter.increment();
counter.increment();
println!("{}", counter.get()); // 2
}

Ref 和 RefMut 是 RefCell 借用的守卫:

use std::cell::RefCell;
fn main() {
let data = RefCell::new(String::from("hello"));
{
let r = data.borrow();
println!("长度: {}", r.len()); // 5
// r 在这里 Drop
}
{
let mut w = data.borrow_mut();
w.push_str(" world");
// w 在这里 Drop
}
println!("{:?}", data);
}

UnsafeCell<T> 是所有内部可变性的底层基础:

特点:

  • UnsafeCell 是 !Sync 的
  • 唯一安全封装是 Cell、RefCell 等
  • 直接操作 UnsafeCell 需要 unsafe

定义:

pub struct UnsafeCell<T> {
value: T,
}

用法:

use std::cell::UnsafeCell;
fn main() {
let cell = UnsafeCell::new(42);
// 获取原始指针需要 unsafe
unsafe {
*cell.get() = 100;
println!("{}", *cell.get()); // 100
}
}
特性Cell<T>RefCell<T>
适用类型Copy任意类型
借用方式复制/移动Ref/RefMut
借用检查编译时运行时
多重借用不支持支持
性能开销极低略高

8. CoerceUnsized - 智能指针强制转换

Section titled “8. CoerceUnsized - 智能指针强制转换”

CoerceUnsized trait 允许智能指针之间的类型强制转换:

标准实现的 CoerceUnsized:

// 允许 [T; N] -> [T]
// 允许 str -> String
// 允许 T -> Box<T>
// 允许 T -> Rc<T>
// 允许 T -> Arc<T>

示例:

use std::rc::Rc;
use std::sync::Arc;
fn main() {
// Box<T> 强制转换为 Box<dyn T>
let boxed: Box<i32> = Box::new(42);
let trait_object: Box<dyn std::fmt::Debug> = boxed;
println!("{:?}", trait_object);
// Rc -> Rc<dyn T>
let rc: Rc<String> = Rc::new(String::from("hello"));
let rc_trait: Rc<dyn std::fmt::Display> = rc;
println!("{}", rc_trait);
}
use std::cell::RefCell;
use std::collections::HashMap;
struct Cache<K, V> {
data: RefCell<HashMap<K, V>>,
hits: RefCell<usize>,
misses: RefCell<usize>,
}
impl<K, V> Cache<K, V>
where
K: std::hash::Hash + Eq,
{
fn new() -> Self {
Cache {
data: RefCell::new(HashMap::new()),
hits: RefCell::new(0),
misses: RefCell::new(0),
}
}
fn get(&self, key: &K) -> Option<&V> {
if self.data.borrow().contains_key(key) {
*self.hits.borrow_mut() += 1;
self.data.borrow().get(key)
} else {
*self.misses.borrow_mut() += 1;
None
}
}
fn insert(&self, key: K, value: V) {
self.data.borrow_mut().insert(key, value);
}
fn stats(&self) -> (usize, usize) {
(*self.hits.borrow(), *self.misses.borrow())
}
}
fn main() {
let cache = Cache::new();
cache.insert("name", "Alice");
cache.insert("age", 30);
println!("{:?}", cache.get(&"name")); // Some("Alice")
println!("{:?}", cache.get(&"addr")); // None
println!("hits: {}, misses: {}", cache.stats().0, cache.stats().1);
}
  1. RefCell 的运行时开销:借用检查在运行时进行,性能敏感代码要注意。

  2. Cell vs RefCell:Cell 适用于 Copy 类型且需要 set/get,RefCell 适用于非 Copy 类型。

  3. UnsafeCell 是底层原语:不应该直接使用,除非实现新的智能指针或标准库特性。

  4. Sync 限制:Cell、RefCell、UnsafeCell 都实现了 !Sync,不能跨线程共享。

  5. RefCell panic 的时机:在持有 Ref 或 RefMut 时尝试再次借用且违反规则时 panic。

std::cell 模块提供了 Rust 内部可变性的核心类型:

类型适用场景借用检查
Cell<T>Copy 类型编译时
RefCell<T>非 Copy 类型运行时
UnsafeCell<T>底层实现手动(unsafe)
CoerceUnsized智能指针转换编译时

合理使用内部可变性可以让 Rust 代码在需要”可变状态”但又想共享数据的场景下更加灵活。