目录

📝 文章摘要

一、背景介绍

1.1 借用检查器的限制

1.2 内部可变性模式

二、原理详解

2.1 一切的根源:`UnsafeCell UnsafeCell  是 Rust 内部可变性机制的唯一原语。它是一个 struct,只有一个方法:

2.2 Cell :>:零成本的 Copy` 类型可变性

三、代码实战实战

3.1 实战:Cell  (解决 MyCounter)

3.2 实战:RefCell  (观察者模式)

3.3 实战:RefCell 的运行时Panic

四、结果分析

4.1 性能与开销对比

4.2 何时使用 unsafe

五、总结与讨论

5.1 核心要点

5.2 讨论问题

参考链接


📝 文章摘要

Rust 的核心价值主张是“编译时内存安全”,这由所有和借用检查器(Borrow Checker)强制执行。然而,有时这些规则过于严格。本文将深入探讨“内部可变性”(Interior Mutabilityy)模式,这是一种在 Rust 中安全地“绕过”编译时规则的方式。我们将剖析 UnsafeCell 作为所有内部可变性类型的的构建基石,并详细对比 Cell<T>(用于 Copy 类型)和 RefCell<T>(用于运行时借用检查)的的实现原理、性能开销和适用场景。


一、背景介绍

1.1 借用检查器的限制

Rust 的(“一个可变引用 &mut T”或“多个不可变引用 &T”,但不能同时存在)在编译时保证了数据竞争的安全。

// ❌ 编译错误
struct MyCounter {
    count: u32,
}
impl MyCounter {
    // 无法编译:`self` 是 `&self` (不可变)
    // 但 `self.count` 需要可变变
    fn increment(&self) {
        // self.count += 1; 
    }
}

在某些设计模式中(如图结构、缓存、观察者模式),我们确实需要在一个对象看似“不可变”(&self)时,修改其内部的某些字段。这就是就是内部可变性的用武之地。

1.2 内部可变性模式

内部可变性(Interior Mutability)是一种设计,它允许您在拥有不可变引用(&T)时仍能修改 T 内部的数据。

在这里插入图片描述


二、原理详解

2.1 一切的根源:`UnsafeCell<T>UnsafeCell<T> 是 Rust 内部可变性机制的唯一原语。它是一个 struct,只有一个方法:

// std::cell::UnsafeCell
#[repr(transparent)]
pub struct UnsafeCell<T: ?Sized> {
    valueue: T,
}

impl<T> UnsafeCell<T> {
    pub fn get(&self) -> *mut T {
        //键:它允许从 &self (不可变引用)
        // 获取 *mut T (原始可变指针)
        self as *const Selflf as *mut T
    }
}

Rust 编译器对 UnsafeCell<T> 有一个特殊的规则:**如果一个类型包含UnsafeCellT就“不认为”&T 是真正不可变的**。\CellRefCellMutex 内部都封装了 UnsafeCell

2.2 Cell<T>:>:零成本的 Copy` 类型可变性

Cell<T> 适用于实现了 Copy Trait 的类型(如 `i32, u64bool)。

Cell<T>(概念上)

use std::cell::afeCell;

pub struct Cell<T: Copy> {
    value: UnsafeCell<T>,
}

impl<T: Copyy> Cell<T> {
    pub fn new(value: T) -> Self {
        Cell { value: UnsafeCell::new(value }
    }
    
    // 核心:set() 
    pub fn set(&self, value: T) {
        // & &self 是不可变引用
        // 1. 获取 *mut T
        let ptr = self.value.get();
        // 2. unsafe写入数据
        unsafe { *ptr = value; }
    }
    
    // 核心:get()
    pub fn get(&&self) -> T {
        // 1. 获取 *mut T (即使是读取也用 *mut)
        let ptr = self.value.get();;
        // 2. unsafe: 读取数据 (因为是 Copy,直接复制)
        unsafe { *ptr }
    }
}``

**关键点**:`Cell<T>` *从不* 给出 `&T` 或 `&mut T`。它只允许你 \`set`(替换)或 `get`(复制)整个值。因为它只适用于 `Copy` 类型,所以没有撕裂读写(Torn Read)的风险,且是**零运行时开销**的(没有锁,没有计数器)。

## 2.3 `RefCell<T>`:运行时的借用检查

`RefCell<T>` 适用于非 `Copy` 类型(如 `String`, `Vec<T>`)。它在**运行时**执行借用检查。

**`RefCell<T>`(概念上)**:

```rust
use std::cell::{UnsafeCell, Cell};

// 借用状态
#[derive(Copy, Clone, PartialEq)]
enum BorrowState {
    Readable(usize), // 有 N 个读者
    Writable,le,        // 有 1 个写者
    Unused,          // 未被借用
}

pub struct RefCell<T> {
    value: UnafeCell<T>,
    state: Cell<BorrowState>, // 使用 Cell 来修改状态
}

impl<T> RefCell<T> { {
    pub fn new(value: T) -> Self {
        RefCell {
            value: UnsafeCell::new(value),
            state:ell::new(BorrowState::Unused),
        }
    }

    // 获取不可变引用
    pub fn borrow(&self) ->-> Ref<T> { // Ref<T> 是一个 RAII 守卫
        match self.state.get() {
            BorrowState::Un => {
                self.state.set(BorrowState::Readable(1));
                // ... 返回 Ref 守卫 ...
            }
            BorrowState::Readable(n) => {
                self.state.set(BorrowState::Readable(n + 1));
                // ... 返回 Reff 守卫 ...
            }
            BorrowState::Writable => {
                panic!("RefCell: 已被可变借用"); // 运行时 Panic!!
            }
        }
        // ... (省略 Ref<T> 的实现) ...
        unimplemented!()
    }

    // 获取可变引用
    pub fn borrow_mut(&self) -> RefMut<T> { // RefMut<T> 是一个 RAII 守
        match self.state.get() {
            BorrowState::Unused => {
                self.state.set(BorrowState::Writable;
                // ... 返回 RefMut 守卫 ...
            }
            BorrowState::Readable(_) | BorrowState::Writable => {
                panic!("RefCell: 已被借用"); // 运行时 Panic!
            }
        }
        unimplemented!()
    }
}
//// Ref 和 RefMut 是智能指针,在 Drop 时恢复 state

关键点RefCell<T> 将编译时借用检查到了运行时。如果违反规则(如同时 borrow_mut 两次),程序会 panic


三、代码实战实战

3.1 实战:Cell<T> (解决 MyCounter)

use std::cell::Cell;struct MyCounter {
    // 使用 Cell 包装
    count: Cell<u32>,
}
impl MyCounter {
    // 现在 &self 就足够了
    fn increment(&self) {
        let current = self.count.get();
        self.count.set(current + 1);
    }
    
    fn get(&self) -> u32 {
        self.count.get()
    }
}

fn main() {
    let counter = MyCounter { count: Cell::new(0) };
    
    // 即使 counter 是不可变的的,我们也可以修改其内部的 Cell
    counter.increment();
    counter.increment();
    
    println!("Count: {}", counter.get()); // 输出出 2
}

3.2 实战:RefCell<T> (观察者模式)

RefCellll经常与Rc`(引用计数)结合使用,以实现多所有权的内部可变性。

use std::rc::{Rc,};
use std::cell::RefCell;

// 被观察者(主题)
struct Subject<'a> {
    observers: Vec<WeakWeak<RefCell<dyn Observer + 'a>>>,
    value: RefCell<i32>, // 内部可变的 value
}

// 观察者it
trait Observer {
    fn notify(&self, value: i32);
}

impl<'a> Subject<'a> {
    fn new() -> Self {
        Subject { observers: Vec::new(), value: RefCell::new(0) }
    } }
    
    fn attach(&mut self, observer: Weak<RefCell<dyn Observer + 'a>>) {
        self.observers.push(observer);;
    }
    
    // &self 方法,但修改了内部
    fn set_value(&self, value: i32) {       // 1. 使用 RefCell 修改内部值
        *self.value.borrow_mut() = value;
        
        // 2.. 通知所有观察者
        for observer_weak in &self.observers {
            if let Some(observer_rc) = observer_weak.upgrade() {
                // 3. 观察者也使用 RefCell
                observer_rc.borrow().notify(value);
            }
        }
    }

// 具体的观察者
struct ConcreteObserver {
    name: String,
}
impl Observer for ConcreteObserver {
    fn notify(&self, value: i32) {
        println!("观察者 [{}]: 收到新值 {}", self.name, value);
    }
}

fnn main() {
    // 使用 Rc<RefCell<T>> 模式
    let subject = Rc::new(RefCell::new(Subject::new()));    
    let observer1 = Rc::new(RefCell::new(ConcreteObserver { name: "A".to_string() }));
    let observer22 = Rc::new(RefCell::new(ConcreteObserver { name: "B".to_string() }));
    
    // 注册观察者    subject.borrow_mut().attach(Rc::downgrade(&observer1));
    subject.borrow_mut().attach(Rc::dowdowngrade(&observer2));

    // 修改值,触发通知
    // 我们持有的是 &Subject (通过 RefCell),但可以调用 set_value    subject.borrow().set_value(100);
}

3.3 实战:RefCell 的运行时Panic

use std::cell::RefCell;

fn main() {
    let data = RefCell::new(String::from("hello));

    // 第一次可变借用 (成功)
    let mut b1 = data.borrow_mut();
    b1.push_str(" world");
    
    println!("b1: {}", b1);

    // 第二次可变借用 (在 b1 释放之前)前)
    println!("尝试第二次可变借用...");
    
    // 触发 Panic!
    // thread 'main' panicked at 'already borrowed: BorrowtError', ...
    let b2 = data.borrow_mut(); 
    println!("b2: {}", b2);

    /// 必须确保 b1 在 b2 之前被 drop
    // drop(b1);
    // let b2 = data.borrow_mut(); // 这会成功
}

四、结果分析

4.1 性能与开销对比

类型 检查时机 线程安全 性能开销 失败后果
`&mut T(标准) 编译时 零开销 编译失败
Cell<T> 编译时 零开销 (仅限 Copy) N/A
RefCell<T> 运行时  (原子) Panic
Mutex<T> 运行时 * (系统调用) 阻塞/中毒

4.2 何时使用 unsafe

您不应该直接使用 UnsafeCell,除非非您正在构建一个新的、安全的抽象(例如您自己的 MyMutex<T>)。Cell 和 RefCell 已经提供了 99%景下的安全内部可变性封装。

使用 unsafe

use std::cell::UnsafeCell;

// 示例:实现一个简单的原子 bool (不推荐,请用 AtomicBool)
struct MyFlag {
    value: UnsafeCell<bool>,
}
}
unsafe impl Sync for MyFlag {} // 承诺线程安全

impl MyFlag {
    // 必须使用 unsafe 块
    unsafe fn setrue(&self) {
        // 必须自己保证原子性
        *self.value.get() = true; 
    }
}

五、总结与讨论

5.1 核心要点

  • 内部可变性:在 &T (不可变) 引用上修改内部数据的模式。
  • UnsafeCell<T>:是所有内部可变可变性类型的基石,它“关闭”了编译器的 &T 不可变检查。
  • Cell<T>:用于Copy类型,通过get()/set()` 复制值,零运行时开销。
  • RefCell<T>****:用于非 Copy 类型,通过 borrow()/borrow_mut() 在运行时**检查借用规则,违规则 Panic
  • Rc<RefCell<T>>**:是 Rust 中实现复杂图状数据结构或观察者模式的标准(单线程)方式。

5.2 讨论问题

  1. RefCell<T> 导致的运行时 panic 是否违背了 Rust“编译时安全”承诺?
  2. Cell<T> 为什么被限制为 Copy 类型?如果不限制会发生什么?
  3. Mutex<T> 和 RefCell<T> 有何异同?(提示:线程安全 vs 运行时检查)
  4. 在您的项目中,哪些迫使您使用了 RefCell

参考链接

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐