智能指针与内部可变性 intermediate 30 分钟 更新 2026-06-15

RefCell 与内部可变性

用 RefCell<T> 在只持有共享引用时修改数据。

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