bigywater.tech
Rust · 33

Cell<T> 与内部可变性

Cell 是内部可变性里最轻的一种:它不借出引用,只能整体读出或整体替换,所以不需要任何运行时检查。这篇验证它的 API、对 T 的要求、内存开销、典型用途,以及为什么它不能跨线程。

4 字节
Cell 的大小,与 u32 相同(RefCell 是 16 字节)
E0599
Cell::get 要求 String: Copy
E0277
Cell 不是 Sync,不能跨线程共享

set、replace、take

实测 · 本机执行输出
  源码:
      1 | use std::cell::Cell;
      2 | fn main() {
      3 |     let c = Cell::new(10);                  // 不是 mut
      4 |     c.set(20);
      5 |     let old = c.replace(30);
      6 |     println!("replace 返回旧值 {},现在的值 {}", old, c.get());
      7 |     let n = c.take();                       // 取出并留下默认值 0
      8 |     println!("take 取出 {},之后 get() = {}", n, c.get());
      9 | }
  编译:成功
  运行(退出码 0):
    replace 返回旧值 20,现在的值 30
    take 取出 30,之后 get() = 0

不借出引用

实测 · 本机执行输出
  源码:
      1 | use std::cell::Cell;
      2 | fn main() {
      3 |     let c = Cell::new(String::from("hi"));
      4 |     let s = c.get();
      5 |     println!("{s}");
      6 | }
  编译:失败
    error[E0599]: the method `get` exists for struct `Cell<String>`, but its trait bounds were not satisfied
       --> <实验目录>/src/u33b.rs:4:15
        |
      4 |     let s = c.get();
        |               ^^^
        |
       ::: <rust 标准库源码>/library/alloc/src/string.rs:353:1
        |
    353 | pub struct String {
        | ----------------- doesn't satisfy `String: Copy`
        |
        = note: the following trait bounds were not satisfied:
                `String: Copy`
    
    error: aborting due to 1 previous error
    
    For more information about this error, try `rustc --explain E0599`.
  源码:
      1 | use std::cell::Cell;
      2 | fn main() {
      3 |     let c = Cell::new(String::from("hi"));
      4 |     let old = c.replace(String::from("new"));
      5 |     println!("旧值 {old},现在 {}", c.take());
      6 | }
  编译:成功
  运行(退出码 0):
    旧值 hi,现在 new

大小

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    u32 = 4,Cell<u32> = 4,RefCell<u32> = 16
    String = 24,Cell<String> = 24

RefCell 见RefCell<T> 运行时借用检查。

&self 方法里更新状态

实测 · 本机执行输出
  源码:
      1 | use std::cell::Cell;
      2 | struct Counter { hits: Cell<u32>, name: String }
      3 | impl Counter {
      4 |     fn lookup(&self, key: &str) -> usize { self.hits.set(self.hits.get() + 1); key.len() + self.name.len() }    // &self,不是 &mut self
      5 | }
      6 | fn main() {
      7 |     let c = Counter { hits: Cell::new(0), name: "cfg".into() };
      8 |     let r1 = &c; let r2 = &c;
      9 |     r1.lookup("a"); r2.lookup("bb"); r1.lookup("ccc");
     10 |     println!("通过两个共享引用共调用 3 次,hits = {}", c.hits.get());
     11 | }
  编译:成功
  运行(退出码 0):
    通过两个共享引用共调用 3 次,hits = 3

不能跨线程

实测 · 本机执行输出
  源码:
      1 | use std::cell::Cell;
      2 | use std::thread;
      3 | fn main() {
      4 |     let c = Cell::new(0);
      5 |     thread::scope(|s| { s.spawn(|| c.set(1)); });
      6 | }
  编译:失败
    error[E0277]: `Cell<i32>` cannot be shared between threads safely
       --> <实验目录>/src/u33f.rs:5:33
        |
      5 |     thread::scope(|s| { s.spawn(|| c.set(1)); });
        |                           ----- ^^^^^^^^^^^ `Cell<i32>` cannot be shared between threads safely
        |                           |
        |                           required by a bound introduced by this call
        |
        = help: the trait `Sync` is not implemented for `Cell<i32>`
        = note: if you want to do aliasing and mutation between multiple threads, use `std::sync::RwLock` or `std::sync::atomic::AtomicI32` instead
        = note: required for `&Cell<i32>` to implement `Send`
    note: required because it's used within this closure
       --> <实验目录>/src/u33f.rs:5:33
        |
      5 |     thread::scope(|s| { s.spawn(|| c.set(1)); });
        |                                 ^^
    …(共 28 行)

线程安全的内部可变性见Mutex<T>与原子类型。

分步演示

Cell<T>
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动
    • 没有验证:OnceCell / LazyCell、Cell<[T]> 与 as_slice_of_cells、UnsafeCell 作为所有内部可变性的基础、Cell 在优化后的汇编(是否和直接读写内存相同)
    京ICP备2026067502号-1京公网安备11010502063487号