Rust - 内部可变性模式

170 阅读3分钟

在 Rust 里,内部可变性模式是一种十分重要的编程模式。它允许我们在拥有不可变引用的情况下对数据进行修改,从一定程度上突破了 Rust 常规的借用规则。

为什么需要内部可变性?

Rust 的默认借用规则要求:

  • 要么有多个不可变引用(&T)
  • 要么有一个可变引用(&mut T)

但有时我们需要在外部不可变的情况下修改内部数据,这时就需要内部可变性模式。 内部可变性模式借助一些特殊类型,像 CellRefCellMutex 等,突破了这一限制。即便只有不可变引用(&T),也能够对数据进行修改。这种模式实际上是把运行时的检查替代了编译时的检查。

实现途径

内部可变性模式主要是通过 UnsafeCell 来达成的。这是一种原始类型,Rust 标准库中的 CellRefCell 等类型都是基于它构建的。UnsafeCell 为我们提供了“在不可变引用下修改数据”的能力,不过,使用它也意味着要自行承担内存安全方面的责任。

常用类型

  1. Cell Cell 适用于实现 Copy 特征的类型,它采用的是值的 get/set 操作方式。
use std::cell::Cell; 
fn main() { 
    let num = Cell::new(5); 
    let reference = # reference.set(10); // 借助不可变引用对值进行修改
    println!("{}", num.get()); // 输出结果为 10 
} 
  1. RefCell

RefCell 适用于那些没有实现 Copy 特征的类型,它在运行时会对借用规则进行检查。要是违反了规则,程序就会触发 panic。

use std::cell::RefCell; 
fn main() { 
    let shared_vec = RefCell::new(vec![1, 2, 3]); 
    let reference = &shared_vec; 
    { 
        let mut mut_borrow = reference.borrow_mut(); // 获取可变引用 
        mut_borrow.push(4); 
    } // 可变引用在此处失效 
    let immut_borrow = reference.borrow(); // 现在可以获取不可变引用 
    println!("{:?}", immut_borrow); // 输出结果为 [1, 2, 3, 4] 
} 
  1. Mutex(用于多线程环境) 在多线程环境中,Mutex 可以保证线程安全,实现内部可变性。
use std::sync::{Mutex, Arc}; 
use std::thread; 
fn main() { 
    let counter = Arc::new(Mutex::new(0)); 
    let handles: Vec<_> = (0..10).map(|_| { 
        let counter = Arc::clone(&counter); 
        thread::spawn(move || { 
            let mut num = counter.lock().unwrap(); 
            *num += 1; 
        }) 
    }).collect(); 
    
    for handle in handles { 
        handle.join().unwrap(); 
    } 
    println!("Result: {}", *counter.lock().unwrap()); // 输出结果为 10 
} 

应用场景

  1. 实现观察者模式 在实现观察者模式时,被观察对象需要能够在不可变的情况下,对观察者列表进行修改。
  2. 包装非线程安全的 API 当需要包装一些非线程安全的 API 时,内部可变性模式可以在单线程环境中提供可变的访问方式。
  3. 实现状态管理 在实现状态管理时,状态对象可能会被多个部分共享,并且需要在不可变的情况下进行修改。

注意要点

  1. 运行时开销 RefCellMutex 在运行时会进行借用检查,这会带来一定的性能开销,所以在性能敏感的场景中需要谨慎使用。
  2. 可能引发的问题 如果使用不当,内部可变性模式可能会破坏 Rust 的内存安全保证,比如引发数据竞争等问题。
  3. 与外部不可变的关系 内部可变性模式只是改变了可变性的位置,从编译时检查转变为运行时检查,但并没有改变 Rust 的内存安全核心。

总结

内部可变性模式是 Rust 中一种非常强大的模式,它能够在遵守 Rust 内存安全规则的前提下,灵活地修改共享数据。不过,这种模式也存在一定的风险,可能会带来运行时开销。因此,在使用时需要谨慎权衡,建议优先考虑使用常规的可变性(&mut),只有在确实需要的时候才选择内部可变性模式。