为什么Rust中的Cell只能用于Copy而不能用于Clone类型



Rust标准库的文档指出Cell只能用于Copy类型,在所有其他情况下都应该使用RefCell,但没有确切解释原因。

在研究了CellRefCell的文档和代码之后,唯一重要的一点似乎是Cellget函数。如果该值是Copy类型,则可以只返回这样的副本。但是为什么克隆还不够好呢?

可以在RefCell:之上直接实现set功能

fn set<T>(r: &RefCell<T>, v: T) {
    *r.borrow_mut() = v
}

只有当没有其他人持有对该值的引用时,这才有效。但如果可以克隆值,就可以这样做:

fn get<T: Clone>(r: &RefCell<T>) -> T {
    r.borrow().clone()
}

Cell这样的类型与Clone类型一起使用可以避免运行时借用检查的开销。我是不是遗漏了什么?

它不健全。DK.的评论是正确的,但你甚至不需要恐慌来造成破坏。一个有问题的场景是:

  1. 单元格(与Option一起(允许创建循环,即自引用类型
  2. Clone实现获得&self引用
  3. 因此,在存在循环的情况下,Clone实现可以访问正在克隆的细胞
  4. 因此,被克隆的对象可以覆盖它自己,而它对自己有一个普通的借用(即&self(
  5. 借贷时过度书写是不健全的,因为它允许任意类型的双关语和其他不良行为。例如,假设有一个最初为Ok(T)Result<T, E>字段,引用内部的T并用Err(R)覆盖Result。则CCD_ 23突然参考CCD_

这个例子归功于Huon Wilson,请参阅user.rust-lang.org线程"为什么Cell需要Copy而不是Clone?"?。他的文章深入探讨了限制的更多结构性原因,还包括一个完整的代码示例。

这是我的观点,但我不能直接将其与存在这种限制的真正原因联系起来。

我认为复制是"廉价的"(例如复制少数位(,克隆是"昂贵的"(如调用函数或更改数据(。如果这样的单元格使用Clone,它将强制在每次使用时复制基础值(cell.get()(。例如,使用CloneCell<Vec<T>>意味着每个cell.get()都需要调用内存分配器。这不是个好主意。

因此,限制使用Copy类型可能是引导人们避免朝自己的脚开枪的一种方式。

公认的答案仍然是完全正确的(而且很有趣(,但我想提到Cell在Rust 1.17中获得的一些额外工具,它们不需要内容是Copy:

  • CCD_ 32交换两个小区的内容
  • Cell::replace在单元格中放入一个新值并返回旧值
  • Cell::takereplace类似,使用Default::default()

注意这里与mem::swapmem::replacemem::take的紧密平行。(尽管实际上最后一个直到Rust 1.40才稳定下来。(Cell方法有效地做了同样的事情,但它们通过共享引用工作,而不需要可变引用。

对于实现Default的类型,我们可以使用Cell::take来完成与.clone()非常相似的事情,只需再执行几个步骤:

fn clone_from_cell<T>(cell: &Cell<T>) -> T
where
    T: Clone + Default,
{
    let val: T = cell.take();
    let clone: T = val.clone();
    cell.set(val);
    clone
}

对于实现Clone但不实现Default的类型(有些不常见,但例如NonZeroU32(,请注意,Option<T>实现Default而与T无关,因此Cell<Option<T>>可以是"0";克隆的";对任何CCD_ 51使用该函数。

最新更新