Module std::cell1.0.0[][src]

Expand description

可共享的可变容器。

Rust 内存安全基于以下规则:给定一个对象 T,它只能具有以下之一:

  • 对对象具有多个不可变引用 (&T) (也称为别名)。
  • 对对象有一个可变引用 (&mut T) (也称为可变性)。

这由 Rust 编译器强制执行。但是,在某些情况下,此规则不够灵活。有时需要对一个对象进行多次引用,然后对其进行可变的。

存在共享的可变容器以允许以受控的方式进行可变性,即使在出现混叠的情况下也是如此。Cell<T>RefCell<T> 都允许以单线程方式执行此操作。 但是,Cell<T>RefCell<T> 都不是线程安全的 (它们不实现 Sync)。 如果需要在多个线程之间进行别名和可变的,则可以使用 Mutex<T>RwLock<T>atomic 类型。

Cell<T>RefCell<T> 类型的值可以通过共享引用 (例如 常见的 &T 类型),而大多数 Rust 类型只能通过唯一的 (&mut T) 引用进行可变的。 我们说 Cell<T>RefCell<T> 提供了内部可变性,而典型的 Rust 类型却表现出继承的可变性。

Cell 类型有两种: Cell<T>RefCell<T>Cell<T> 通过将值移入和移出 Cell<T> 来实现内部可变性。 要使用 quot 代替值,必须使用 RefCell<T> 类型,在可变之前获取一个写锁。Cell<T> 提供了检索和更改当前内部值的方法:

  • 对于实现 Copy 的类型,get 方法检索当前内部值。
  • 对于实现 Default 的类型,take 方法将当前内部值替换为 Default::default(),然后返回替换后的值。
  • 对于所有类型,replace 方法将替换当前内部值并返回替换后的值,而 into_inner 方法将消耗 Cell<T> 并返回内部值。 此外,set 方法替换内部值,丢弃替换后的值。

RefCell<T> 使用 Rust 的生命周期来实现 动态借用,一个人可以申请临时的、独占的、附加访问内在值的过程。 借用 forRefCell<T>s 是在 运行时 被跟踪的,这与 Rust 的原生引用类型不同,后者在编译时是完全静态跟踪的。 由于RefCell` 借用是动态的,因此可以尝试借用已经可变借用的值; 发生这种情况时,将导致线程 panic。

何时选择内部可变性

更常见的继承的可变性 (其中必须具有对值的唯一访问权) 是使 Rust 能够强烈考虑指针别名的关键语言元素之一,从而可以静态地防止崩溃错误。 因此,首选继承的可变性,而内部可变性则是不得已而为之。 由于 cell 类型能够在不允许的情况下实现可变,所以有时内部可变性可能是合适的,或者甚至必须使用,例如

  • 在不可变的内部引入可变性
  • 逻辑上不可变的方法的实现细节。
  • Clone 的变异实现。

在不可变的内部引入可变性

许多共享的智能指针类型,包括 Rc<T>Arc<T>,都提供了可以在多方之间克隆和共享的容器。 由于所包含的值可能具有多重别名,因此只能使用 &,而不能使用 &mut 来借用它们。 如果没有 cell,根本不可能改变这些智能指针内的数据。

然后,在共享指针类型中放置一个 RefCell<T> 来重新引入可变性是非常常见的:

use std::cell::{RefCell, RefMut};
use std::collections::HashMap;
use std::rc::Rc;

fn main() {
    let shared_map: Rc<RefCell<_>> = Rc::new(RefCell::new(HashMap::new()));
    // 创建一个新块以限制动态借用的作用域
    {
        let mut map: RefMut<_> = shared_map.borrow_mut();
        map.insert("africa", 92388);
        map.insert("kyoto", 11837);
        map.insert("piccadilly", 11826);
        map.insert("marbles", 38);
    }

    // 请注意,如果我们没有让缓存的上一次借用离开作用域,那么后续的借用将导致动态线程 panic。
    //
    // 这是使用 `RefCell` 的主要危险。
    let total: i32 = shared_map.borrow().values().sum();
    println!("{}", total);
}
Run

请注意,这个例子使用了 Rc<T> 而不是 Arc<T>RefCell<T>s 适用于单线程场景。如果在多线程情况下需要共享可变性,可以考虑使用 RwLock<T>Mutex<T>

逻辑上的不可变方法的实现细节

有时,可能希望不要在 API 中公开幕后发生了变异。 这可能是因为逻辑上该操作是不可变的,但是例如,缓存会强制实现执行变异; 或者是因为您必须使用突变来实现一个最初定义为接受 &self 的 trait 方法。

use std::cell::RefCell;

struct Graph {
    edges: Vec<(i32, i32)>,
    span_tree_cache: RefCell<Option<Vec<(i32, i32)>>>
}

impl Graph {
    fn minimum_spanning_tree(&self) -> Vec<(i32, i32)> {
        self.span_tree_cache.borrow_mut()
            .get_or_insert_with(|| self.calc_span_tree())
            .clone()
    }

    fn calc_span_tree(&self) -> Vec<(i32, i32)> {
        // 昂贵的计算在这里
        vec![]
    }
}
Run

Clone 的变异实现

这只是一种特殊情况 - 但很常见 - 以前的情况:隐藏看起来不可变的操作的可变性。 clone 方法不会更改源值,并声明采用 &self,而不是 &mut self。 因此,在 clone 方法中发生的任何变异的都必须使用 cell 类型。 例如,Rc<T>Cell<T> 中维护它的引用计数。

use std::cell::Cell;
use std::ptr::NonNull;
use std::process::abort;
use std::marker::PhantomData;

struct Rc<T: ?Sized> {
    ptr: NonNull<RcBox<T>>,
    phantom: PhantomData<RcBox<T>>,
}

struct RcBox<T: ?Sized> {
    strong: Cell<usize>,
    refcount: Cell<usize>,
    value: T,
}

impl<T: ?Sized> Clone for Rc<T> {
    fn clone(&self) -> Rc<T> {
        self.inc_strong();
        Rc {
            ptr: self.ptr,
            phantom: PhantomData,
        }
    }
}

trait RcBoxPtr<T: ?Sized> {

    fn inner(&self) -> &RcBox<T>;

    fn strong(&self) -> usize {
        self.inner().strong.get()
    }

    fn inc_strong(&self) {
        self.inner()
            .strong
            .set(self.strong()
                     .checked_add(1)
                     .unwrap_or_else(|| abort() ));
    }
}

impl<T: ?Sized> RcBoxPtr<T> for Rc<T> {
   fn inner(&self) -> &RcBox<T> {
       unsafe {
           self.ptr.as_ref()
       }
   }
}
Run

Structs

RefCell::try_borrow 返回的错误。

可变的内存位置。

RefCell box 中将借用的引用括起来。 从 RefCell<T> 不变借来的值的包装器类型。

具有动态检查借用规则的可变内存位置

RefCell<T> 可变借来的值的包装器类型。

Rust 中内部可变性的核心原语。