-
Notifications
You must be signed in to change notification settings - Fork 172
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
对书中值语义、引用语义、栈拷贝、按位复制等概念的澄清 #104
Comments
一些内容仍然和我的理解有所偏差,我仍然认为无需关联映射起来。
操作其中一个,要不要影响另一个,是业务策略问题,不存在安全与否。除了Copy外,还有lifetime用来保证内存操作安全。 既然上文都指出了&T已经是特例了,那我们自然可以在Copy上实现更多引用语义, 比如: use std::ops::Deref;
#[derive(Copy, Clone)]
struct Cat {
age: u8
}
#[derive(Copy, Clone)]
struct DancingCat<'a>(&'a Cat);
impl Cat {
fn say(&self) {
println!("Meow! I am {:p}", &self);
}
}
impl<'a> Deref for DancingCat<'a> {
type Target = Cat;
fn deref(&self) -> &Cat {
self.0
}
}
fn main() {
let cat = Cat{ age: 0 };
let cat1 = DancingCat(&cat);
let cat2 = cat1; // 实现Copy,但是是引用语义的DancingCat
cat1.say();
cat2.say();
} 因此:
用”对应“来描述,都太强关联了一点,可以用"类似"这样的词语来描述就没有那么绝对。 |
另外,这句话仍然读不通顺:
直译其文档就好:
|
@kvingwang 感谢建议和示例。有些言辞是可以继续修改的。 大致上没有太多错误即可,方便读者去理解就好了。万事总能找出特例,但书里侧重点是在于阐述共性。 |
个人反对这样实现 Deref …… |
@mzji |
不是不好,而是 Deref / DerefMut 已经限定给 |
Rust 社区很强调语义行为,不遵循语义搞东西容易被“开除”出社区,超麻烦的 |
|
不知道你是不是误解了我上面的例子是为了实现继承?并不是的,我根本没考虑继承,DancingCat没有包含Cat。 |
可以说这种需要出现概率颇低,而且一旦这么使用就得在文档里注明。个人接触过的 crate 里,不按标准库语义实现 Deref / DerefMut 的例子不多。(与其说“开除”,不如说“自绝于”?)
&T 和 &mut T 是指针语义,这里没有问题
大略看了一下 servo 里面对于 Deref / DerefMut 的实现,除了个别用途不明(因为包裹的类型看不懂)的情况以外,其余 1) 要么是真·智能指针 2) 要么是纯 type wrapper ,这两个情况都算符合 Deref 用途 |
只是单纯举例( Deref 语义实现不匹配的例子),不是说你这段代码是这样的。 |
说起来 servo 里确实有一处 deref 的用法值得商榷:
https://github.com/servo/servo/blob/master/components/script/dom/bindings/num.rs#L37 std 里的 NonZero* 等类型都没有实现 deref ,这里却实现了……感觉是把编译时进行的行为检测移到了运行时 |
不用删掉”智能“两个字,你之前也引用过,官方reference里原话就是智能指针。 好吧,既然你自行给Deref扩充了should应用范围,从智能指针到普通指针,再增加了wrapper的情况。 那么,我上面的例子就是wrapper + 指针的合体,是否可以认为我们观点已经达成了一致:我上面的示例的Deref方式并没有问题?
且不论你这个概率颇低说法是否能站得住脚。注意,我这里不是在推荐一种写代码的pattern,而是举例说明有那么一些情况他是不符合Copy与值语义对应的,这自然要举反例,与现实需要这种场景出现频率的高低、是否是常规用法并无太大关系,你反对的方向搞错了。
没那么严重,这些都是你自己想象的。不过,这与主题无关,不希望从这方面展开讨论。 |
很多 Deref / DerefMut 的实现都打了智能指针的嘴巴子,特别是 &T / &mut T ,鉴于“随便加特例以满足限定要求”这种行为本身就是乱搞,我觉得把智能指针语义降低到指针语义是合理的。以后我再写 Deref / DerefMut 的实现,也会限定到指针语义(话又说回来,智能指针本身也是指针语义的一种附加限定/扩充)。
对于你所说的“这里并不是严格对应关系”我十分赞同。更进一步说,我反对在讲解 Rust 的(初级)材料中引入值语义/引用语义的说明,因为 1) Rust 本身没有提到这两个概念,也就是说要理解 Rust 并不需要引入这两个概念; 2) 这两个概念在其他语言中具有比在 Rust 中更强的作用。我只是反对用 Deref / DerefMut 举例而已。(总觉得我给自己挖了个坑)我其实想说的就是“虽然可以这么搞,但是这么搞的人 1) 要么清楚的知道TA在干什么(并愿意为此负责) 2) 要么已经完了”大概这样。
我个人一直认为不按语义实现 trait 的性质并不比滥用 unsafe 的程度来的更低……开除出社区都是轻的,应该挂城墙以儆效尤。当然,这是个人意见。以下不再进行这方面的讨论。 |
好吧,既然你觉得Rust在Deref、智能指针这方面规定得比较混乱,那你应该去给官方反应一下,我也赞同Rust需要把这些东西屡清楚一点。 鉴于Servo里面有大量的我示例中类似的Deref用法,我觉得我们在原主题的观点上是一致的。 至于社区对”未遵循Deref文档所描述的should的行为“的态度,我和你的看法不一样,我们可以另外找个地方讨论。 |
好的!通过和你的交流我受益良多,非常感谢 |
讨论真激烈 |
最终的说明:
use std::ops::Deref;
#[derive(Copy, Clone)]
struct Cat {
age: u8
}
#[derive(Copy, Clone)]
struct DancingCat<'a>(&'a Cat);
impl Cat {
fn say(&self) {
println!("Meow! I am {:p}", &self);
}
}
impl<'a> Deref for DancingCat<'a> {
type Target = Cat;
fn deref(&self) -> &Cat {
self.0
}
}
fn main() {
let cat = Cat{ age: 0 };
let cat1 = DancingCat(&cat);
let cat2 = cat1; // 实现Copy,但是是引用语义的DancingCat
cat1.say();
cat2.say();
}
|
如果还要讨论,就继续讨论吧,但这个issues已经关闭。 |
值语义和引用语义按我的理解应该起名为"直接语义"和"间接语义". |
@dwing4g 各人有各人的理解吧,书中使用值语义、引用语义,只是帮助读者从过去的概念中方便迁移到Rust的Copy和Move中,便于理解。 |
最近,由读者朋友 @kvinwang 对本书第五章所有权系统中出现的「按位复制」、「栈复制」、「值类型」、「值语义」和「引用语义」等概念提出了质疑,并且指出了这些概念混乱使用的问题。
经过连续多天的讨论,今天整理出结果来一致澄清一下这些概念。
编译器默认自动调用x的clone方法
修改为:
说明: 其实这里说「自动调用x的clone方法」,是为了方便读者理解这种默认行为。对于Rust中Copy的语义,开发者是无法修改的。也就是说,对于赋值、或者传参等行为发生的时候,实现Copy的类型默认是按位复制。开发者自己实现Copy trait,必须也实现clone方法。至于clone方法是如何实现的不重要,重要的是,它们必须有按位复制的能力。但是标准库文档里建议你只需要实现按位复制即可。注意,这里指的是隐式调用clone的行为,而非显式调用clone方法。
按位复制和栈复制
其实书里问题的根源在于,我当时错误地将「按位复制」理解为「栈复制」。虽然按「栈复制」来理解Rust中的Copy行为,也没有什么影响。但确实不太严谨。
所以,首先需要明确「按位复制」,等同于C语言里的memcpy。 所以,我将书里出现的相关批注做了修改:
按位复制,只是复制「值」,而不会复制「值」中包含指针指向的数据。也可以说,它是浅复制的一种特定形式。它不会进行深复制。拿Rust中的String字符串来说,其本质是一个智能指针,在栈上存储着元信息,但是在堆里存储的具体的数据。如果对其进行按位复制,只会复制其栈上的元信息,而不会复制其堆里的数据。如果想深复制,只能显式地调用其clone函数。
所以,这是我书里没有说明清楚的一个地方。 因为Rust默认是在栈上存储的,所以,按位复制通常都是发生在栈上复制。但是按位复制,并不一定只能复制栈上的数据。
对于值类型和引用类型的修改如下:
对于值语义和引用语义的修改如下:
「Copy语义和Move语义」 vs 「值语义、引用语义」
说明: 这几段,主要是澄清Rust中的Copy语义。Copy的重点在于,是否可以安全地进行按位复制。实际上,要不要把它看成值语义或引用语义,都是看你自己。书里,只是给你提供一个视角,也方便你把Rust中的新概念「Copy语义」和「Move语义」与旧知识「值语义」和「引用语义」挂上钩。这样,即方便你理解所有权机制,又重点体现了,Rust以「内存安全」为设计原则对这门语言的精巧设计。
以上。
The text was updated successfully, but these errors were encountered: