Rust 之 Cell 相关详解

"深入理解Rust内部可变性的轻量级解决方案:Cell与RefCell"

Posted by Vlor on November 25, 2025

概述

在Rust的内存安全模型中,内部可变性是一种特殊的设计模式,它允许我们在持有不可变引用的情况下修改数据。 Cell<T> 作为实现这一模式的轻量级工具,通过值的复制/移动语义而非引用实现内部修改,为简单类型提供了高效的可变性解决方案。与 RefCell<T> 不同, Cell<T> 专为 Copy 类型优化,不提供运行时借用检查,因此具有更高的性能和更严格的使用限制。

核心概念

什么是内部可变性?

Rust的所有权系统通常要求:

  • 要么拥有一个可变引用( &mut T)- 要么拥有任意数量的不可变引用( &T) 内部可变性打破了这一规则,允许通过不可变引用修改数据,其核心实现依赖于 UnsafeCell<T>——所有内部可变性类型的底层基础。

Cell与UnsafeCell的关系

Cell<T>UnsafeCell<T> 的安全封装:

// Cell的简化定义
pub struct Cell<T> {
    value: UnsafeCell<T>,
}

// UnsafeCell是所有内部可变性的基础
pub struct UnsafeCell<T> {
    // 实际存储的数据
    data: T,
}

UnsafeCell<T> 是Rust中唯一允许内部可变性的原语,它的特殊之处在于:

  • 提供 get() 方法返回 *mut T 裸指针- 不实现 Sync trait,因此不能跨线程共享- 是 CellRefCellMutex 等类型的底层构建块

    Cell详解

定义与基本特性

Cell<T> 的完整定义位于 std::cell 模块:

pub struct Cell<T> {
    // 私有字段,无法直接访问
    value: UnsafeCell<T>,
}

关键特性

  • 仅适用于Copy类型:要求 T: Copy(除了 get_mut() 方法)
  • 值语义:通过复制/替换而非引用操作数据
  • 无运行时开销:不进行借用检查,无panic风险-
  • 单线程使用:不实现 Sync,不能跨线程共享

常用方法解析

new():创建Cell实例

use std::cell::Cell;

// 创建包含i32的Cell
let cell = Cell::new(42);

get():获取当前值(T: Copy)

let cell = Cell::new(42);
let value = cell.get(); // 复制值,value = 42

set():设置新值(T: Copy)

let cell = Cell::new(42);
cell.set(100); // 替换为新值
assert_eq!(cell.get(), 100);

replace():替换值并返回旧值

let cell = Cell::new(42);
let old_value = cell.replace(100); // old_value = 42
assert_eq!(cell.get(), 100);

take():取出值并留下默认值(T: Default)

let cell = Cell::new(42);
let value = cell.take(); // value = 42,cell变为0(i32的默认值)
assert_eq!(cell.get(), 0);

get_mut():获取可变引用(&mut self)

当拥有 Cell 的可变引用时,可以直接访问内部值:

let mut cell = Cell::new(42);
let inner = cell.get_mut(); // &mut i32
*inner = 100;
assert_eq!(cell.get(), 100);

代码示例:基本用法

use std::cell::Cell;

fn main() {
    let cell = Cell::new(10);

    // 读取值
    assert_eq!(cell.get(), 10);

    // 修改值
    cell.set(20);
    assert_eq!(cell.get(), 20);

    // 替换值并获取旧值
    let old = cell.replace(30);
    assert_eq!(old, 20);
    assert_eq!(cell.get(), 30);

    // 取出值并留下默认值
    let taken = cell.take();
    assert_eq!(taken, 30);
    assert_eq!(cell.get(), 0); // i32的默认值
}

代码示例:结构体中的Cell

use std::cell::Cell;

struct Counter {
    count: Cell<i32>,
    name: &'static str,
}

impl Counter {
    fn new(name: &'static str) -> Self {
        Counter {
            count: Cell::new(0),
            name,
        }
    }

    fn increment(&self) {
        // 通过不可变引用修改内部数据
        let current = self.count.get();
        self.count.set(current + 1);
    }

    fn get_count(&self) -> i32 {
        self.count.get()
    }
}

fn main() {
    let counter = Counter::new("click");
    counter.increment();
    counter.increment();
    assert_eq!(counter.get_count(), 2);
}

Cell与RefCell的对比

特性 Cell RefCell
适用类型 T: Copy 任意T
访问方式 通过值复制/移动 通过引用(&T/&mut T)
借用检查 无(编译期限制) 运行时检查(可能panic)
性能 极高(无额外开销) 中等(引用计数维护)
典型用途 简单类型(i32, bool等) 复杂类型(Vec, String等)
错误处理 编译期错误 运行时panic
内存安全 完全安全(Copy语义保障) 相对安全(运行时检查)

代码示例:Cell与RefCell的选择

use std::cell::{Cell, RefCell};

// 适合用Cell的场景:简单Copy类型
struct Stats {
    hits: Cell<u32>,
    misses: Cell<u32>,
}

// 适合用RefCell的场景:非Copy类型
struct Cache {
    data: RefCell<Vec<String>>,
}

impl Cache {
    fn new() -> Self {
        Cache {
            data: RefCell::new(Vec::new()),
        }
    }

    fn add_entry(&self, entry: String) {
        // 需要运行时借用检查
        let mut data = self.data.borrow_mut();
        data.push(entry);
    }
}

实践应用

适用场景

  • 小型状态管理:如计数器、标志位等简单状态
   // 线程局部存储中的计数器
   thread_local! {
       static REQUEST_COUNT: Cell<u32> = Cell::new(0);
   }

   // 使用示例
   REQUEST_COUNT.with(|cell| {
       cell.set(cell.get() + 1);
   });

  • 结构体内部状态:在保持结构体不可变的同时修改内部字段
   struct Logger {
       enabled: Cell<bool>,
       // 其他不可变字段...
   }

   impl Logger {
       fn enable(&self) {
           self.enabled.set(true);
       }

       fn disable(&self) {
           self.enabled.set(false);
       }
   }

  • 性能敏感场景:当需要最小开销的可变性时
   // 性能计数器
   struct PerformanceMonitor {
       cycles: Cell<u64>,
       instructions: Cell<u64>,
   }

   impl PerformanceMonitor {
       fn record_cycle(&self) {
           self.cycles.set(self.cycles.get() + 1);
       }
   }

不适用场景

  • 非Copy类型:需要使用 RefCell<T> 或其他类型
   // 错误示例:String不是Copy类型
   // let cell = Cell::new(String::from("hello"));
   // cell.set(String::from("world")); // 编译错误

  • 需要引用而非值:当需要传递数据引用时
   // 错误示例:无法获取Cell内部的引用
   // let cell = Cell::new(42);
   // let ref_val: &i32 = &cell.get(); // 只能获取值的副本

  • 多线程环境:Cell不实现Sync,不能跨线程共享
   // 错误示例:Cell不能跨线程
   // use std::thread;
   // let cell = Cell::new(42);
   // thread::spawn(move || {
   //     cell.set(100);
   // }).join(); // 编译错误

常见错误与解决方案

错误1:对非Copy类型使用Cell

// 编译错误:String没有实现Copy
use std::cell::Cell;

struct User {
    name: Cell<String>, // ❌ 错误:String: !Copy
    age: Cell<u32>,     // ✅ 正确:u32: Copy
}

解决方案:使用 RefCell<String> 代替

错误2:尝试获取Cell内部的引用

// 编译错误:无法获取Cell内部的引用
use std::cell::Cell;

let cell = Cell::new(42);
let value: &i32 = &cell.get(); // ❌ 只能获取值的副本

解决方案:如果确实需要引用,使用 RefCell<T>

错误3:在多线程中使用Cell

// 编译错误:Cell不实现Sync
use std::cell::Cell;
use std::thread;

let cell = Cell::new(0);
thread::spawn(move || { // ❌ Cell不能在线程间传递
    cell.set(1);
});

解决方案:使用 Mutex<T>RwLock<T> 等线程安全类型

与其他可变性方案的对比

类型 可变性机制 线程安全 运行时开销 适用场景
&mut T 可变引用 不适用 单一所有者修改
Cell<T> 值复制/移动 Copy类型,简单修改
RefCell<T> 运行时借用检查 低(引用计数) 非Copy类型,复杂借用
Mutex<T> 互斥锁 中(系统调用) 多线程共享修改
RwLock<T> 读写锁 中(系统调用) 多线程读写分离

总结要点

  • Cell专为Copy类型设计,通过值语义实现内部可变性
  • 无运行时开销,不进行借用检查,性能优于RefCell
  • 不可跨线程使用,不实现Sync trait
  • 核心方法get()/ set() 用于值访问, replace()/ take() 用于值替换
  • 适用场景:简单状态管理、计数器、标志位等轻量级可变需求
  • 底层依赖:所有功能基于 UnsafeCell<T> 实现,完全安全

进阶资源

性能对比

操作 Cell RefCell Mutex
读取操作 ~1ns ~2ns ~20ns
写入操作 ~1ns ~2ns ~30ns
替换操作 ~1ns ~2ns ~35ns