RefCell 与内部可变性
你会学到什么
RefCell<T>把借用检查从编译期推迟到运行期。- 即使只持有
&self,也能通过borrow_mut()修改内部数据。 Rc<RefCell<T>>组合出“多个所有者 + 可变”的常见模式。
最小示例
use std::cell::RefCell;
let cell = RefCell::new(vec![1, 2]);
cell.borrow_mut().push(3); // 通过共享引用修改
运行代码
cd examples
cargo run -p rt_24_refcell
cargo test -p rt_24_refcell
代码讲解
borrow() 拿只读引用,borrow_mut() 拿可变引用。借用规则仍然成立,只是改成运行时检查:
fn log(&self, message: &str) {
self.messages.borrow_mut().push(message.to_string());
}
注意 log 的参数是 &self 而不是 &mut self——这就是“内部可变性”。
Rc<RefCell<T>> 把两者结合:Rc 提供多个所有者,RefCell 提供可变性:
let logger = Rc::new(Logger::new());
let clone = Rc::clone(&logger);
clone.log("hi"); // 改动对 logger 也可见
常见错误
运行时违反借用规则会 panic 而不是编译错误:
let a = cell.borrow_mut();
let b = cell.borrow_mut(); // ❌ already borrowed: panic at runtime
确保上一个借用先离开作用域。这是 RefCell 相比编译期检查的代价。
练习
- 用
Rc<RefCell<i32>>实现一个被多处共享的计数器。 - 故意制造一次运行时双重借用,观察 panic 信息。
小结
RefCell 用运行时检查换来内部可变性,Rc<RefCell<T>> 是单线程共享可变状态的经典组合。
下一步
接下来进入并发阶段,学习线程、channel 和 Arc<Mutex<T>>。
完整示例代码
下面是 examples/24_refcell/ 的完整源码。无需 clone 仓库,直接在页面上阅读、复制、对照运行。
examples/24_refcell/src/main.rs
//! RefCell<T>:把借用检查从编译期推迟到运行期(内部可变性)。
use std::cell::RefCell;
use std::rc::Rc;
/// 即使只持有 &self,也能通过 RefCell 修改内部状态。
struct Logger {
messages: RefCell<Vec<String>>,
}
impl Logger {
fn new() -> Self {
Self {
messages: RefCell::new(Vec::new()),
}
}
fn log(&self, message: &str) {
self.messages.borrow_mut().push(message.to_string());
}
fn count(&self) -> usize {
self.messages.borrow().len()
}
}
fn main() {
// Rc<RefCell<T>>:多个所有者共享同一份可变数据。
let logger = Rc::new(Logger::new());
let clone = Rc::clone(&logger);
logger.log("started");
clone.log("working");
logger.log("done");
println!("logged {} messages", logger.count());
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn logs_through_shared_reference() {
let logger = Rc::new(Logger::new());
let clone = Rc::clone(&logger);
logger.log("a");
clone.log("b");
assert_eq!(logger.count(), 2);
}
#[test]
fn interior_mutability_without_mut() {
let logger = Logger::new(); // 注意:不是 mut
logger.log("x");
assert_eq!(logger.count(), 1);
}
} examples/24_refcell/Cargo.toml
[package]
name = "rt_24_refcell"
version.workspace = true
edition.workspace = true
publish.workspace = true