Rust 内部可变性

在共享引用下安全变更:Cell/RefCell 运行时借用检查;与 Mutex 的单线程/多线程分工。

#type / concept #status / growing #tech / dev #resource / rust

[!info] 关联笔记

Rust 内部可变性

这个概念为什么出现

有时 API 只能拿到 &self,但仍需变更内部缓存/计数。
内部可变性把借用检查推迟到运行时(单线程 RefCell)或用原子/锁(多线程)。

[!abstract] 一句话理解 RefCell<T> 允许在 &self 下借出 &T/&mut T,违反规则则运行时 panic;Cell 适合 Copy 的替换语义。

最小可运行示例

场景:只读服务门面下的命中计数

配置服务对外 &self 查询,内部统计命中次数。

use std::cell::RefCell;

// 业务意图:不可变方法中更新计数。
// 教学点:RefCell borrow_mut;运行时检查。

struct ConfigService {
    hits: RefCell<u64>,
    region: String,
}

impl ConfigService {
    fn region(&self) -> &str {
        *self.hits.borrow_mut() += 1;
        &self.region
    }
    fn hits(&self) -> u64 { *self.hits.borrow() }
}

fn main() {
    let svc = ConfigService { hits: RefCell::new(0), region: String::from("cn") };
    println!("region={}", svc.region());
    println!("hits={}", svc.hits());
}

建议运行:cargo run

期望:

region=cn
hits=1

结合场景再看三个关注点

  1. svc 本身无需 mut
  2. 嵌套 borrow 冲突会 panic
  3. 多线程用 Mutex 而非 RefCell

核心概念与准确模型

类型场景
Cell<T>Copy 或替换
RefCell<T>单线程动态借用
Mutex/RwLock多线程
OnceCell/LazyLock延迟初始化

设计动机

  • 表达“逻辑共享但内部记账”
  • 保持外部 API 简洁
  • 仍有安全兜底(panic 而非 UB)

边界与误区

  • 把 RefCell 当默认 → 隐藏别名可变复杂度
  • 持有 RefMut 跨越危险边界
  • Rc 组合形成图时的环

[!warning] 常见误区:RefCell 让程序在多线程自动安全 否,RefCell 不是 Sync 共享方案。

工程实践

  1. 优先重构所有权
  2. 计数/缓存等局部用内部可变
  3. 测试覆盖双 borrow 路径
  4. 多线程明确换锁

本节总结

  • 内部可变 = 运行时借用规则
  • Cell/RefCell/Mutex 分工
  • 慎用但有价值

自测题

  1. RefCell 违反借用规则后果?
  2. 为何常与 Rc 组合?
参考答案
  1. 运行时 panic。
  2. 共享所有权节点上需要可变子字段时。

延伸阅读与资料来源

资料类型支撑内容
The Book — RefCell官方书内部可变性
std::cell标准库Cell 模块
创建于 2026/7/15 更新于 2026/7/15