Ch 6: std::cell - 内部可变性
内部可变性(Interior Mutability)是 Rust 中一种设计模式,允许在持有不可变引用的同时修改数据。本章将详细讲解 Cell、RefCell、UnsafeCell 和 CoerceUnsized 四种核心类型,理解它们的工作原理和使用场景。
2. 什么是内部可变性
Section titled “2. 什么是内部可变性”在 Rust 中,通常的规则是:要么拥有不可变数据(&T),要么拥有可变数据(&mut T)。内部可变性打破了这个规则:
// 普通情况:&T 不能修改数据let x = 5;let r = &x;// *r = 10; // 编译错误!
// 内部可变性:即使持有 &T 也可以修改use std::cell::Cell;let x = Cell::new(5);let r = &x;r.set(10); // OK!println!("{}", x.get()); // 103. Cell - 简单值的内部可变性
Section titled “3. Cell - 简单值的内部可变性”Cell<T> 提供了一种简单的内部可变性,适用于实现了 Copy 的类型:
特点:
get()方法获取值的拷贝set()方法设置新值- 替代方案:
replace()替换值并返回旧值 - 替代方案:
take()取出值并放默认值
示例:
use std::cell::Cell;
fn main() { let cell = Cell::new(5);
// get - 获取值的拷贝 println!("{}", cell.get()); // 5
// set - 设置新值 cell.set(10); println!("{}", cell.get()); // 10
// replace - 替换并返回旧值 let old = cell.replace(20); println!("old = {}, new = {}", old, cell.get()); // old = 10, new = 20
// take - 取出值,放默认值 let taken = cell.take(); println!("taken = {}, now = {:?}", taken, cell.get()); // taken = 20, now = 0}Cell 的限制: 只能用于 Copy 类型:
use std::cell::Cell;
// Cell<String> 无法编译,因为 String 不是 Copy// let cell = Cell::new(String::from("hello"));// cell.set(String::from("world")); // 错误!4. RefCell - 引用计数的内部可变性
Section titled “4. RefCell - 引用计数的内部可变性”RefCell<T> 用于提供内部可变性,适用于非 Copy 类型:
特点:
- 运行时借用检查
borrow()返回Ref<T>borrow_mut()返回RefMut<T>- 借用违反规则时 panic
示例:
use std::cell::RefCell;
fn main() { let data = RefCell::new(vec![1, 2, 3]);
// borrow - 不可变借用 let v = data.borrow(); println!("{:?}", v); // [1, 2, 3]
// borrow_mut - 可变借用 data.borrow_mut().push(4);
// 再次借用 let v = data.borrow(); println!("{:?}", v); // [1, 2, 3, 4]}借用规则违反时 panic:
use std::cell::RefCell;
fn main() { let data = RefCell::new(5);
let r1 = data.borrow(); let r2 = data.borrow(); // 两个不可变借用,OK
println!("r1 = {}, r2 = {}", r1, r2);
// 不可变借用仍在作用域内 // let r3 = data.borrow_mut(); // panic!同时有不可变和可变借用}RefCell 的典型用法:
use std::cell::RefCell;
struct Counter { value: RefCell<i32>,}
impl Counter { fn new() -> Self { Counter { value: RefCell::new(0) } }
fn increment(&self) { *self.value.borrow_mut() += 1; }
fn get(&self) -> i32 { *self.value.borrow() }}
fn main() { let counter = Counter::new(); counter.increment(); counter.increment(); println!("{}", counter.get()); // 2}5. Ref 和 RefMut - 借用守卫
Section titled “5. Ref 和 RefMut - 借用守卫”Ref 和 RefMut 是 RefCell 借用的守卫:
use std::cell::RefCell;
fn main() { let data = RefCell::new(String::from("hello"));
{ let r = data.borrow(); println!("长度: {}", r.len()); // 5 // r 在这里 Drop }
{ let mut w = data.borrow_mut(); w.push_str(" world"); // w 在这里 Drop }
println!("{:?}", data);}6. UnsafeCell - 底层内部可变性
Section titled “6. UnsafeCell - 底层内部可变性”UnsafeCell<T> 是所有内部可变性的底层基础:
特点:
UnsafeCell是!Sync的- 唯一安全封装是
Cell、RefCell等 - 直接操作
UnsafeCell需要unsafe
定义:
pub struct UnsafeCell<T> { value: T,}用法:
use std::cell::UnsafeCell;
fn main() { let cell = UnsafeCell::new(42);
// 获取原始指针需要 unsafe unsafe { *cell.get() = 100; println!("{}", *cell.get()); // 100 }}7. RefCell vs Cell - 选择指南
Section titled “7. RefCell vs Cell - 选择指南”| 特性 | Cell<T> | RefCell<T> |
|---|---|---|
| 适用类型 | Copy | 任意类型 |
| 借用方式 | 复制/移动 | Ref/RefMut |
| 借用检查 | 编译时 | 运行时 |
| 多重借用 | 不支持 | 支持 |
| 性能开销 | 极低 | 略高 |
8. CoerceUnsized - 智能指针强制转换
Section titled “8. CoerceUnsized - 智能指针强制转换”CoerceUnsized trait 允许智能指针之间的类型强制转换:
标准实现的 CoerceUnsized:
// 允许 [T; N] -> [T]// 允许 str -> String// 允许 T -> Box<T>// 允许 T -> Rc<T>// 允许 T -> Arc<T>示例:
use std::rc::Rc;use std::sync::Arc;
fn main() { // Box<T> 强制转换为 Box<dyn T> let boxed: Box<i32> = Box::new(42); let trait_object: Box<dyn std::fmt::Debug> = boxed; println!("{:?}", trait_object);
// Rc -> Rc<dyn T> let rc: Rc<String> = Rc::new(String::from("hello")); let rc_trait: Rc<dyn std::fmt::Display> = rc; println!("{}", rc_trait);}9. 实战示例:Cache 实现
Section titled “9. 实战示例:Cache 实现”use std::cell::RefCell;use std::collections::HashMap;
struct Cache<K, V> { data: RefCell<HashMap<K, V>>, hits: RefCell<usize>, misses: RefCell<usize>,}
impl<K, V> Cache<K, V>where K: std::hash::Hash + Eq,{ fn new() -> Self { Cache { data: RefCell::new(HashMap::new()), hits: RefCell::new(0), misses: RefCell::new(0), } }
fn get(&self, key: &K) -> Option<&V> { if self.data.borrow().contains_key(key) { *self.hits.borrow_mut() += 1; self.data.borrow().get(key) } else { *self.misses.borrow_mut() += 1; None } }
fn insert(&self, key: K, value: V) { self.data.borrow_mut().insert(key, value); }
fn stats(&self) -> (usize, usize) { (*self.hits.borrow(), *self.misses.borrow()) }}
fn main() { let cache = Cache::new();
cache.insert("name", "Alice"); cache.insert("age", 30);
println!("{:?}", cache.get(&"name")); // Some("Alice") println!("{:?}", cache.get(&"addr")); // None
println!("hits: {}, misses: {}", cache.stats().0, cache.stats().1);}10. 需要注意的点
Section titled “10. 需要注意的点”-
RefCell的运行时开销:借用检查在运行时进行,性能敏感代码要注意。 -
CellvsRefCell:Cell适用于Copy类型且需要set/get,RefCell适用于非Copy类型。 -
UnsafeCell是底层原语:不应该直接使用,除非实现新的智能指针或标准库特性。 -
Sync限制:Cell、RefCell、UnsafeCell都实现了!Sync,不能跨线程共享。 -
RefCellpanic 的时机:在持有Ref或RefMut时尝试再次借用且违反规则时 panic。
11. 总结
Section titled “11. 总结”std::cell 模块提供了 Rust 内部可变性的核心类型:
| 类型 | 适用场景 | 借用检查 |
|---|---|---|
Cell<T> | Copy 类型 | 编译时 |
RefCell<T> | 非 Copy 类型 | 运行时 |
UnsafeCell<T> | 底层实现 | 手动(unsafe) |
CoerceUnsized | 智能指针转换 | 编译时 |
合理使用内部可变性可以让 Rust 代码在需要”可变状态”但又想共享数据的场景下更加灵活。