
RefCell 和 Cell:我虽然是 immutable,但我能改!
Rust 的所有权规则里有条铁律:不可变引用不能修改数据。
但有一天,你写了个结构体:
struct Cache {
data: Vec<String>,
}
impl Cache {
fn get(&self, key: &str) -> Option<&String> {
// 只是想查个缓存...
// 但顺便想记录一下访问次数...
// 等等,self 是不可变借用,不能改啊!
}
}
你: "我就想记个访问次数,咋就不行了?"
Rust: "规则就是规则,&self 不能改!"
你: "可这数据明明是我自己的啊!"
这时候,内部可变性登场了。它就像 Rust 规则里的"后门":外面看着是不可变的,但里面偷偷能改。
今天咱们就来看看这个"精神分裂"的玩法。
外部不可变,内部可变。
就像个存钱罐:
use std::cell::RefCell;
struct Counter {
value: RefCell<i32>, // 内部可变
}
impl Counter {
fn new() -> Self {
Counter {
value: RefCell::new(),
}
}
fn increment(&self) { // &self 不可变借用
*self.value.borrow_mut() += ; // 但里面能改!
}
fn get(&self) -> i32 {
*self.value.borrow()
}
}
fn main() {
let counter = Counter::new();
counter.increment(); // &self 调用
counter.increment(); // 还能调用
println!("计数:{}", counter.get()); // 输出:2
}
看到了吗? increment 用的是 &self(不可变借用),但居然能修改 value!这就是内部可变性的魔法。
Rust 的借用检查通常在编译时进行:
fn main() {
let mut x = ;
let r1 = &x;
let r2 = &x;
// let m1 = &mut x; // ❌ 编译错误!
}
但 RefCell 把检查挪到了运行时:
use std::cell::RefCell;
fn main() {
let x = RefCell::new();
let r1 = x.borrow(); // 不可变借用
let r2 = x.borrow(); // 还可以
// let m1 = x.borrow_mut(); // ❌ 运行时 panic!
println!("r1={}, r2={}", *r1, *r2);
}
编译时检查 vs 运行时检查:
特性 | 编译时检查 | 运行时检查 (RefCell) |
|---|---|---|
检查时机 | 编译阶段 | 程序运行 |
性能 | 零开销 | 有小开销 |
错误处理 | 编译错误 | panic |
灵活性 | 严格 | 灵活 |
RefCell 的人设: "编译时我管不了,运行时我说了算。"
核心方法:
borrow() → 不可变借用(返回 Ref<T>)borrow_mut() → 可变借用(返回 RefMut<T>)get_mut() → 直接获取可变引用(需要 &mut self)use std::cell::RefCell;
fn main() {
let data = RefCell::new();
// 不可变借用
let r = data.borrow();
println!("值:{}", *r);
// r 离开作用域,借用结束
// 可变借用
let mut m = data.borrow_mut();
*m = ;
// m 离开作用域,借用结束
println!("新值:{}", *data.borrow()); // 输出:10
}
借用规则(运行时检查):
use std::cell::RefCell;
fn main() {
let data = RefCell::new();
let r1 = data.borrow();
let r2 = data.borrow(); // ✅ 多个不可变
// let m = data.borrow_mut(); // ❌ panic!
drop(r1);
drop(r2);
let m = data.borrow_mut(); // ✅ 现在可以了
*m = ;
}
Cell 的人设: "我只存简单类型,但我也能改。"
RefCell vs Cell:
RefCell<T> → 返回借用,可以多次访问Cell<T> → 拷贝值,适合简单类型use std::cell::Cell;
fn main() {
let value = Cell::new();
value.set(); // 修改
let v = value.get(); // 获取(拷贝)
println!("值:{}", v); // 输出:10
}
Cell 的限制:
Copy 类型(数字、布尔等)use std::cell::Cell;
fn main() {
let c = Cell::new();
// ❌ 不能借用
// let r = c.borrow();
// ✅ 只能拷贝
let v = c.get();
c.set();
}
这就是上篇树形结构里的组合:
use std::rc::Rc;
use std::cell::RefCell;
#[derive(Debug)]
struct TreeNode {
value: i32,
children: RefCell<Vec<Rc<TreeNode>>>,
}
fn main() {
let root = Rc::new(TreeNode {
value: ,
children: RefCell::new(vec![]),
});
// 可以修改 children,即使 root 是不可变的
root.children.borrow_mut().push(
Rc::new(TreeNode {
value: ,
children: RefCell::new(vec![]),
})
);
println!("根节点:{:?}", root);
}
为什么需要这个组合?
Rc → 共享所有权(多个人拥有)RefCell → 内部可变(共享时还能修改)use std::cell::RefCell;
fn main() {
let data = RefCell::new(vec![, , ]);
// 不可变借用读取
println!("原始:{:?}", data.borrow());
// 可变借用修改
data.borrow_mut().push();
data.borrow_mut().push();
println!("修改后:{:?}", data.borrow());
// 输出:[1, 2, 3, 4, 5]
}
use std::cell::Cell;
fn main() {
let counter = Cell::new();
for _ in .. {
let current = counter.get();
counter.set(current + );
}
println!("计数:{}", counter.get()); // 输出:5
}
use std::cell::RefCell;
struct User {
name: String,
login_count: RefCell<u32>, // 内部可变
}
impl User {
fn new(name: &str) -> Self {
User {
name: name.to_string(),
login_count: RefCell::new(),
}
}
fn login(&self) { // &self 不可变
let mut count = self.login_count.borrow_mut();
*count += ;
println!("{} 第 {} 次登录", self.name, *count);
}
fn get_login_count(&self) -> u32 {
*self.login_count.borrow()
}
}
fn main() {
let user = User::new("Larry");
user.login(); // &self 调用
user.login();
user.login();
println!("总登录次数:{}", user.get_login_count());
}
// 输出:
// Larry 第 1 次登录
// Larry 第 2 次登录
// Larry 第 3 次登录
// 总登录次数:3
use std::cell::RefCell;
fn main() {
let data = RefCell::new();
let r1 = data.borrow();
let r2 = data.borrow();
// 这时候再要可变借用就会 panic
let result = std::panic::catch_unwind(|| {
let mut m = data.borrow_mut(); // ❌ panic!
*m = ;
});
if result.is_err() {
println!("捕获到 panic:同时存在不可变和可变借用!");
}
// 释放所有借用后可以正常操作
drop(r1);
drop(r2);
*data.borrow_mut() = ;
println!("新值:{}", *data.borrow());
}
use std::rc::{Rc, Weak};
use std::cell::RefCell;
#[derive(Debug)]
struct Node {
value: i32,
parent: RefCell<Option<Weak<Node>>>, // 弱引用父节点
children: RefCell<Vec<Rc<Node>>>,
}
fn main() {
let root = Rc::new(Node {
value: ,
parent: RefCell::new(None),
children: RefCell::new(vec![]),
});
let child = Rc::new(Node {
value: ,
parent: RefCell::new(Some(Rc::downgrade(&root))), // 弱引用
children: RefCell::new(vec![]),
});
// 添加子节点
root.children.borrow_mut().push(Rc::clone(&child));
println!("root 计数:{}", Rc::strong_count(&root)); // 1
println!("child 计数:{}", Rc::strong_count(&child)); // 1
// 可以通过弱引用访问父节点
if let Some(parent) = child.parent.borrow().upgrade() {
println!("child 的父节点值:{}", parent.value);
}
}
Weak 的特点:
upgrade() 获取 RcRc 已经释放,upgrade() 返回 Noneuse std::cell::RefCell;
fn main() {
let data = RefCell::new();
let r = data.borrow();
let m = data.borrow_mut(); // ❌ 运行时 panic!
println!("值:{}", *m);
}
运行时错误:
thread 'main' panicked at library/std/src/cell.rs:1631:25:
already borrowed: BorrowMutError
翻译: "已经有人在借了,你还要可变借用?想都别想!"
use std::cell::RefCell;
fn main() {
let data = RefCell::new(vec![, , ]);
let r = data.borrow();
// 忘记 drop(r)
let m = data.borrow_mut(); // ❌ panic! r 还在借用
// ✅ 正确做法
{
let r = data.borrow();
println!("{:?}", *r);
} // r 在这里释放
let m = data.borrow_mut(); // ✅ 现在可以了
}
use std::cell::RefCell;
fn main() {
let data = RefCell::new(vec![, , , , ]);
// ❌ 错误:借用跨越循环
let borrowed = data.borrow();
for i in .. {
if i == {
data.borrow_mut().clear(); // ❌ panic!
}
}
// ✅ 正确:借用限制在作用域内
for i in .. {
if i == {
data.borrow_mut().clear();
} else {
println!("{}", data.borrow()[i]);
}
}
}
use std::cell::Cell;
fn main() {
// ❌ 不能存 String(非 Copy)
// let c = Cell::new(String::from("hello"));
// ✅ 只能存 Copy 类型
let c = Cell::new();
let c2 = Cell::new(true);
}
use std::cell::RefCell;
use std::collections::HashMap;
struct Cache {
data: RefCell<HashMap<String, String>>,
}
impl Cache {
fn new() -> Self {
Cache {
data: RefCell::new(HashMap::new()),
}
}
fn get_or_insert(&self, key: &str, value: &str) -> &str {
// 先检查有没有
{
let cache = self.data.borrow();
if let Some(v) = cache.get(key) {
return v;
}
}
// 没有就插入
{
let mut cache = self.data.borrow_mut();
cache.insert(key.to_string(), value.to_string());
}
// 返回刚插入的值
let cache = self.data.borrow();
cache.get(key).unwrap()
}
fn stats(&self) -> usize {
self.data.borrow().len()
}
}
fn main() {
let cache = Cache::new();
println!("{}", cache.get_or_insert("name", "Larry"));
println!("{}", cache.get_or_insert("name", "Larry")); // 命中缓存
println!("{}", cache.get_or_insert("age", "25"));
println!("缓存条目数:{}", cache.stats());
}
use std::cell::RefCell;
use std::rc::Rc;
trait Observer {
fn update(&self, value: i32);
}
struct Subject {
value: i32,
observers: RefCell<Vec<Rc<dyn Observer>>>,
}
impl Subject {
fn new(value: i32) -> Self {
Subject {
value,
observers: RefCell::new(vec![]),
}
}
fn attach(&self, observer: Rc<dyn Observer>) {
self.observers.borrow_mut().push(observer);
}
fn set_value(&self, value: i32) {
self.value = value;
// 通知所有观察者
for observer in self.observers.borrow().iter() {
observer.update(value);
}
}
}
struct Logger;
impl Observer for Logger {
fn update(&self, value: i32) {
println!("日志:值变为 {}", value);
}
}
fn main() {
let subject = Rc::new(Subject::new());
let logger = Rc::new(Logger);
subject.attach(logger);
subject.set_value();
subject.set_value();
}
// 输出:
// 日志:值变为 10
// 日志:值变为 20

核心要点:
选择指南:
RefCellCellRc<RefCell<T>>Weak下篇预告:
内部可变性玩够了,咱们来聊聊 Rust 的高级 Trait。关联类型是啥?默认泛型参数怎么用?特征对象又是啥黑科技?下篇一起揭开 Trait 的高级玩法!
互动问题:
你觉得内部可变性是 Rust 的"漏洞"还是"特性"?有没有被运行时 panic 坑过?评论区聊聊!