[第五章]clone方法按位复制概念错误
kvinwang opened this issue · 29 comments
页码与行数
- 第122页
编译器会默认自动调用x的clone方法
对于实现Copy的类型,其clone方法必须是按位复制的
实际上
- 实现Copy的类型,“赋值”的时候不会调用clone方法(clone也不能代表按位复制)
- 实现Copy的类型,clone并不是必须按位复制的方式实现(实际是随便怎么实现,根据业务需求)
- 即便必须,也不能推出y = x 会调用clone
--- edit ----
PS. Copy也不一定用于栈拷贝, 比如:
use std::cell::RefCell;
fn main() {
let a = Box::new(RefCell::new(1));
let b = Box::new(RefCell::new(2));
*b.borrow_mut() = *a.borrow();
println!("b = {}", b.borrow());
}
@kvinwang 感谢反馈。这个地方我再继续仔细斟酌一下,看看怎么把它描述的更加清晰精准。
P121页
「按位复制就是指栈复制,也叫浅复制,它只复制栈上的数据。相对而言,深复制就是对
栈上和堆上的数据一起复制。」
这个描述是对按位复制的误解,需要修正。以及修正章节中对「栈复制」和「按位复制」混乱使用的问题。
读者按照「按位复制」的理解是没有问题的。
P121页
「按位复制就是指栈复制,也叫浅复制,它只复制栈上的数据。相对而言,深复制就是对
栈上和堆上的数据一起复制。」这个描述是对按位复制的误解,需要修正。以及修正章节中对「栈复制」和「按位复制」混乱使用的问题。
读者按照「按位复制」的理解是没有问题的。
这句话不只“按位复制就是指栈复制”一点问题哦,和浅复制、深复制对应起来也是不对的。
@kvinwang 是的,这句话是要修正的更加精准
根据官方文档, Copy 类型的 clone 实现应该是 trivial 的:
Types that are Copy should have a trivial implementation of Clone. More formally: if T: Copy, x: T, and y: &T, then let x = y.clone(); is equivalent to let x = *y;. Manual implementations should be careful to uphold this invariant; however, unsafe code must not rely on it to ensure memory safety.
同时, Copy 类型的语义就是按位拷贝:
Types whose values can be duplicated simply by copying bits.
也即 Copy 类型的 clone 实现也应当是按位拷贝的。 见下方回复
如果 clone 和 copy 行为不一致,即违反了标准库约定的语义。(但是我忘了是不是 UB) 确实不是 UB
@mzji 嗯,现在主要问题是通用概念里「按位拷贝」、「栈拷贝」、「浅复制」和「深复制」没有说清楚。
主要是说明
- 实现Copy的类型,“赋值”的时候不会调用clone方法(clone也不能代表按位复制)
- 实现Copy的类型,clone并不是必须按位复制的方式实现(实际是随便怎么实现,根据业务需求)
- 即便必须,也不能推出y = x 会调用clone
这里的第一点的括号内的部分和第二点是有问题的
之前我看过一些材料,提到过说对于 Copy 类型,调用它的 clone 方法会直接被编译器优化成按位拷贝
至于栈拷贝,不建议再提这个概念了,不是很重要
至于浅复制和深复制,更多的只和包含堆存储的类型相关,如果展开描述时也应注意这一点
根据官方文档, Copy 类型的 clone 实现应该是 trivial 的:
Types that are Copy should have a trivial implementation of Clone. More formally: if T: Copy, x: T, and y: &T, then let x = y.clone(); is equivalent to let x = *y;. Manual implementations should be careful to uphold this invariant; however, unsafe code must not rely on it to ensure memory safety.
首先“应该"并不代表"必须", 凡是必须的,都应该是编译器来保证。如果必须,Rust完全可以禁止Copy类型自定义实现clone,因为必须一致嘛,自定义就没有意义了。
文档同样说了unsafe code must not rely on it
,就是说clone是不能保证这个语义的,依赖它是不安全的。
同时, Copy 类型的语义就是按位拷贝:
Types whose values can be duplicated simply by copying bits.
也即 Copy 类型的 clone 实现也应当是按位拷贝的。
如果 clone 和 copy 行为不一致,即违反了标准库约定的语义。
即便我们遵从”应当“,文档的意思比较模糊,写文档的人应该没考虑到这里的表意有些不妥。
你可以考虑下面的方式实现clone是否属于"应当"描述的范畴:
struct A(i8);
impl Copy for A {}
impl Clone for A {
fn clone(&self) -> A {
A(self.0)
}
}
我认为这样实现并没有什么问题,但是它已经不是按位复制
了。
但是我忘了是不是 UB
当然不是UB,凡是safe rust出现UB,都属于bug。
这里引用的原文, should 应当理解为 must 。
Manual implementations should be careful to uphold this invariant.
至于那个例子是否算是“应当”,只看它满不满足判断标准即可
if T: Copy, x: T, and y: &T, then let x = y.clone(); is equivalent to let x = *y;
也即 clone 的结果应当与按位拷贝一致。
如仍有疑问,我建议在 rust repo 发 issue ,要求澄清此处语义。 见下方新回复
@kvingwang 追求严谨
这里引用的原文, should 应当理解为 must
unsafe code must not rely on it
已经说明了should不应该理解为must。
unsafe code 不允许依赖于此行为又没啥问题,因为总可以用按位拷贝语义。
已找到官方文档对这一点的描述: RFC 1521: Copy Clone semantics
It's generally been an unspoken rule of Rust that a
clone
of aCopy
type is equivalent to amemcpy
of that type; however, that fact is not documented anywhere. This fact should be in the documentation for theClone
trait, just like the fact thatT: Eq
should implementa == b == c == a
rules.
因此修正我的观点,应当是“clone 的结果”与按位拷贝一致,没有约束具体实现。这样可以方便 rustc 将 Copy 类型的 clone 优化为按位拷贝(因为结果一致)。
Due to RFC 1521, we're allowed to assume that a
Clone
impl forCopy
types is trivial and can be optimized out. Having the compiler automatically add trivialmemcpy
Clone
implementation for allCopy
types should be safe. As well there shouldn't be a backwards compatibility hazard as it would only allow more code to compile.
其他相关资料:
Rust issue #31086
Rust issue #31085
Clippy PR #580 为 Copy 类型手写 clone impl 会被 lint
@mzji 谢谢你的资料。这些资料里大量使用了should。
关于should怎么理解,资料里也有提到:
rust-lang/rust#33420
3. SHOULD This word, or the adjective "RECOMMENDED", mean that there
may exist valid reasons in particular circumstances to ignore a
particular item, but the full implications must be understood and
carefully weighed before choosing a different course.
也就是说允许存在一些特殊场景不遵守这个约定。
按照定义来说确实如此,不过考虑到实现的语义我还是建议做一个 trivial 的实现,这样便于优化。
而且对于 Copy 类型来说 clone 实现不一致在语义角度上来说可能会比较令人困扰。
不如这样说:非极端特殊情况,不要手写 Copy 类型的 clone 实现,应使用 derive 让编译器自行生成 clone 实现,以利于 1) 优化代码 2) 维持语义一致性 。
感觉越描越黑了,Copy类型"赋值"时就是按位复制,"浅复制"等其它概念我明天在另一个issue里面举例说明一下吧
Rust 本身没怎么提到深复制和浅复制,大多是时候只是强调 Copy 类型和 Move 类型之间的区别,最多说一句 clone 行为可自定,多了都没提
@ZhangHanDong 我在 #77 里面给了些我的理解,还是觉得没必要强行关联理解。
Rust中只有实现了Copy的类型才有资格讲值语义/引用语义。未实现Copy的类型,"赋值"时永远只会move,值语义对它们没有意义。
@kvinwang 辛苦了。
也不是强行关联。主要是想和旧知识挂钩,这样方便理解新概念。 我再继续修正一下,力求清晰准确。
我认为是 Rust 有意淡化之前的浅拷贝和深拷贝的概念
为什么呢?因为浅拷贝和深拷贝是相对概念,而且在不同的类型系统中行为不同,有时候很难被正确理解。因此,不如只讨论 Move type / Copy type 、 clone 及其语义,这样简单又实用。
假如有一天这几个概念不够用了,那么再构建新语义模型不迟。
我认为是 Rust 有意淡化之前的浅拷贝和深拷贝的概念
为什么呢?因为浅拷贝和深拷贝是相对概念,而且在不同的类型系统中行为不同,有时候很难被正确理解。因此,不如只讨论 Move type / Copy type 、 clone 及其语义,这样简单又实用。
假如有一天这几个概念不够用了,那么再构建新语义模型不迟。
同意
深浅拷贝概念一般在一些复合数据结构的时候比较有用,推广到一般数据类型意义不大。
合并到这个issues讨论: 对书中值语义、引用语义、栈拷贝、按位复制等概念的澄清
@kvinwang 感谢