rust语言学习笔记(指针六)Cell<T>(内部可变(非指针))

  • 允许你在拥有不可变引用(&T)的情况下修改内部数据,从而绕过 Rust 严格的借用规则限制。
  • Rust 的默认规则是:‌要么有一个可变引用 (&mut T),要么有多个不可变引用 (&T),但不能同时存在。

6.1 关键特性

  • 单线程专用 ‌:Cell<T> 没有实现 Sync trait,因此不能在线程间安全共享。
  • 零运行时开销‌:没有锁,没有借用计数器,性能极高。
  • 仅限 Copy 类型 ‌:通常用于实现了 Copy trait 的类型(如 i32, u64, bool, f64 等)。

6.2 常用方法

方法 签名 说明
new Cell::new(value) 创建一个新的 Cell
get cell.get() -> T 复制 ‌出内部值(要求 T: Copy
set cell.set(value) 替换‌内部值为新的值
replace cell.replace(value) -> T 替换内部值,并返回‌旧值
into_inner cell.into_inner() -> T 消耗 Cell,取出内部值的所有权
take cell.take() -> T 取出内部值,并用 Default::default() 填充(要求 T: Default

6.2 常规用法

rust 复制代码
use std::cell::Cell;

fn main() {
    let data = Cell::new(0);    // 无需标记 mut 可实现可变
    data.set(100);              // 修改值
    println!("{}", data.get()); // 获取值
}

6.3 结构体用法

rust 复制代码
use std::cell::Cell;
#[derive(Debug)]
struct Data {
    value: Cell<i32>,
}
impl Data {
    fn new(value: i32) -> Self {
        Self {
            value: Cell::new(value),
        }
    }
    fn get(&self) -> i32 {
        self.value.get()          // 获取值
    }
    fn set(&self, value: i32) {   // 无需 &mut self
        self.value.set(value);    // 设置值
    }
}

fn main() {
    let data = Data::new(0);
    data.set(100);
    println!("{}", data.get());
}

6.4 Cell<T> vs RefCell<T>

虽然两者都提供内部可变性,但适用场景截然不同。

表格

特性 Cell<T> RefCell<T>
适用类型 必须实现 Copy (如整数、布尔、指针) 任意类型 (如 String, Vec, 自定义结构体)
访问方式 按值拷贝 ‌ (get/set) 按引用借用 ‌ (borrow/borrow_mut)
检查时机 编译期‌保证安全 运行时‌检查借用规则
性能开销 零开销‌ (无计数器,无分支预测失败风险) 有运行时开销 (维护借用计数器)
Panics? 永远不会因借用规则 panic 如果违反借用规则 (如同时可变和不可变借用),会 panic
典型场景 简单状态、标志位、计数器 复杂数据结构、树/图节点、需要引用语义的场景

选择建议:

  • 如果类型是 Copy 且逻辑简单,‌优先使用 Cell。它更快、更安全(不会运行时崩溃)。
  • 如果类型不是 Copy,或者你需要获取内部数据的引用(例如遍历一个列表),则必须使用 RefCell

6.5 常见误区与注意事项

6.5.1 Cell 不适用于大尺寸类型

由于 get() 会复制整个值,如果 T 很大(如大型数组或结构体),get() 的性能会很差。此时应考虑 RefCell 或其他模式。

6.5.2 Cell 不是智能指针

Cell<T> 没有实现 DerefDerefMut,因此你不能像使用 BoxRc 那样直接使用 *cell 或调用内部方法。你必须显式调用 get()set()

6.5.3 线程安全

Cell<T>不是线程安全的 ‌。如果你需要在多线程环境中共享可变状态,应使用 Mutex<T>AtomicU32 等原子类型。

6.5.4 不要滥用内部可变性

内部可变性是 Rust 的"逃生舱口"。在设计 API 时,应优先遵循标准的所有权和借用规则。只有当静态借用检查器确实无法表达你的意图(如在不可变接口中更新缓存或计数)时,才使用 Cell


6.6 总结

  • Cell<T> 是什么? 一个允许在不可变引用下修改内部值的容器。
  • 什么时候用? 当你要修改的数据是 Copy 类型(如 i32, bool),且希望避免运行时借用检查开销时。
  • 怎么用? 使用 set() 修改值,使用 get() 读取值。
  • 核心优势: 零运行时成本,编译期安全,代码简洁。

通过合理使用 Cell<T>,你可以在保持 Rust 内存安全 guarantees 的同时,写出更灵活、更符合直觉的代码结构。