为什么 Rust 中的 Cell 只能用于 Copy 而不能用于 Clone 类型?
Why can Cell in Rust only be used for Copy and not Clone types?
Rust 标准库的文档指出 Cell
只能用于 Copy
类型,在所有其他情况下应该使用 RefCell
,但没有准确解释为什么。
在研究了 Cell
和 RefCell
的文档和代码之后,唯一重要的一点似乎是 Cell
的 get
函数。如果该值是 Copy
类型,那么就可以 return 这样的副本。但为什么克隆不够好?
可以直接在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
类型一起工作可以避免 运行 次借用检查的开销。我在这里遗漏了什么吗?
这是我的意见,但我不能直接将其与存在此类限制的真正原因联系起来。
我认为副本是 "cheap"(例如复制少量位),克隆是 "expensive"(例如进行函数调用或更改数据)。如果这样的单元格使用 Clone
,它将 强制要求 在每次使用时复制基础值 (cell.get()
)。例如,使用 CloneCell<Vec<T>>
意味着每个 cell.get()
都 需要调用内存分配器 。这不是个好主意。
因此,限制 Copy
类型可能是一种引导人们避免搬起石头砸自己脚的方法。
这是不健全的。 DK 的评论。是在正确的轨道上,但你甚至不需要恐慌就可以造成破坏。一个有问题的场景是这样的:
- 单元格(连同
Option
)允许创建循环,即自引用类型
Clone
实现获得 &self
引用
- 在存在循环的情况下,
Clone
实现可能因此访问正在克隆的单元格
- 因此被克隆的对象可以覆盖自己,同时它有一个普通的借用给自己(即,
&self
)
- 借用时覆盖是不合理的,因为它允许任意类型的双关语和其他不良行为。例如,假设有一个
Result<T, E>
字段最初是 Ok(T)
,引用里面的 T
并用 Err(R)
覆盖 Result
。然后 &T
突然引用了一个 E
值。
此示例归功于 Huon Wilson,请参阅 user.rust-lang.org 线程 Why does Cell require Copy instead of Clone?。他的文章深入探讨了这些限制的更多结构性原因,还包括一个完整的代码示例。
接受的答案仍然是完全正确的(而且很吸引人),但我想提一下 Cell
在 Rust 1.17 中获得的一些额外工具,它们不需要内容 Copy
:
Cell::swap
交换两个单元格的内容。
Cell::replace
将新值放入单元格,returns 旧值。
Cell::take
类似于 replace
,使用 Default::default()
值。
请注意此处与 mem::swap
、mem::replace
和 mem::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>>
可以使用该函数“克隆”任何 T
。
Rust 标准库的文档指出 Cell
只能用于 Copy
类型,在所有其他情况下应该使用 RefCell
,但没有准确解释为什么。
在研究了 Cell
和 RefCell
的文档和代码之后,唯一重要的一点似乎是 Cell
的 get
函数。如果该值是 Copy
类型,那么就可以 return 这样的副本。但为什么克隆不够好?
可以直接在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
类型一起工作可以避免 运行 次借用检查的开销。我在这里遗漏了什么吗?
这是我的意见,但我不能直接将其与存在此类限制的真正原因联系起来。
我认为副本是 "cheap"(例如复制少量位),克隆是 "expensive"(例如进行函数调用或更改数据)。如果这样的单元格使用 Clone
,它将 强制要求 在每次使用时复制基础值 (cell.get()
)。例如,使用 CloneCell<Vec<T>>
意味着每个 cell.get()
都 需要调用内存分配器 。这不是个好主意。
因此,限制 Copy
类型可能是一种引导人们避免搬起石头砸自己脚的方法。
这是不健全的。 DK 的评论。是在正确的轨道上,但你甚至不需要恐慌就可以造成破坏。一个有问题的场景是这样的:
- 单元格(连同
Option
)允许创建循环,即自引用类型 Clone
实现获得&self
引用- 在存在循环的情况下,
Clone
实现可能因此访问正在克隆的单元格 - 因此被克隆的对象可以覆盖自己,同时它有一个普通的借用给自己(即,
&self
) - 借用时覆盖是不合理的,因为它允许任意类型的双关语和其他不良行为。例如,假设有一个
Result<T, E>
字段最初是Ok(T)
,引用里面的T
并用Err(R)
覆盖Result
。然后&T
突然引用了一个E
值。
此示例归功于 Huon Wilson,请参阅 user.rust-lang.org 线程 Why does Cell require Copy instead of Clone?。他的文章深入探讨了这些限制的更多结构性原因,还包括一个完整的代码示例。
接受的答案仍然是完全正确的(而且很吸引人),但我想提一下 Cell
在 Rust 1.17 中获得的一些额外工具,它们不需要内容 Copy
:
Cell::swap
交换两个单元格的内容。Cell::replace
将新值放入单元格,returns 旧值。Cell::take
类似于replace
,使用Default::default()
值。
请注意此处与 mem::swap
、mem::replace
和 mem::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>>
可以使用该函数“克隆”任何 T
。