9.3 RefCell<T> 与内部可变性
听
Rust 的借用规则在编译期禁止同时存在可变引用和不可变引用,以确保内存安全。然而,有时程序逻辑需要在仅有不可变引用的情况下修改数据,例如在共享状态中更新计数器或缓存。为此,Rust 提供了 RefCell<T>,它通过运行时借用检查实现“内部可变性”(Interior Mutability)。
什么是内部可变性
通常,要修改一个值,必须拥有其可变引用(&mut T)。但 RefCell<T> 允许你通过不可变引用来获得可变访问权限,将借用规则的检查从编译期推迟到运行时。如果违反规则(如同时持有多个可变借用),程序会在运行时 panic,而非产生未定义行为。
基本用法
RefCell<T> 提供两个关键方法:
borrow():返回Ref<T>(类似&T);borrow_mut():返回RefMut<T>(类似&mut T)。
示例:
use std::cell::RefCell;
let counter = RefCell::new(0);
{
let mut num = counter.borrow_mut();
*num += 1;
}
println!("counter: {}", counter.borrow()); // 输出: 1
尽管 counter 本身是不可变绑定,我们仍能通过 borrow_mut() 修改其内容。
运行时借用检查
RefCell 在内部维护一个借用状态计数器。以下代码会 panic:
let c = RefCell::new(5);
let b1 = c.borrow(); // 不可变借用
let b2 = c.borrow_mut(); // ❌ 尝试可变借用,但已有不可变借用
// 程序在此处 panic:already borrowed: BorrowMutError
这种检查发生在运行时,因此不会阻止编译,但保证了安全性。
与 Rc<T> 结合实现共享可变状态
RefCell 常与 Rc<T> 搭配,用于单线程下多个所有者共享并修改同一数据:
use std::rc::Rc;
use std::cell::RefCell;
#[derive(Debug)]
struct Node {
value: i32,
children: RefCell<Vec<Rc<Node>>>,
}
let root = Rc::new(Node {
value: 1,
children: RefCell::new(vec![]),
});
let child = Rc::new(Node {
value: 2,
children: RefCell::new(vec![]),
});
root.children.borrow_mut().push(child);
这里,children 字段是 RefCell<Vec<...>>,使得即使通过 Rc 共享的 Node 也能在其子节点列表中添加新元素。
性能与适用场景
- 开销:每次
borrow或borrow_mut都涉及运行时检查(原子操作在单线程中为非原子,但仍需分支判断); - 适用场景:
- 实现 mock 对象或测试中的可变状态;
- 构建图、树等需要父子双向引用的数据结构;
- 在函数式风格中局部突破不可变限制。
限制
RefCell<T>不是线程安全的(未实现Sync),不能跨线程共享;- 不能用于需要静态借用检查的高性能热点路径;
- 违反借用规则会导致 panic,不适合对可靠性要求极高的生产逻辑(除非能确保借用模式安全)。
小结
RefCell<T> 是 Rust 在保持内存安全前提下,对借用规则的一种灵活补充。它通过运行时检查换取编程灵活性,特别适合单线程中需要“在不可变外壳下修改内部”的场景。与 Rc<T> 组合后,能有效表达复杂的共享可变状态。理解其工作原理和限制,有助于在不破坏 Rust 安全模型的前提下,解决实际工程问题。
#Rust 入门教程
分享于 1 周前