tao-of-rust-codes
tao-of-rust-codes copied to clipboard
「第三章」`3.4.2 泛型约束` 数学角度描述 trait 中的概念混淆与逻辑错误
页码与行数
- 第 70 页
-
理解 trait 限定
文本或排版错误
文中说
trait 也是一种类型,是一种方法集合,或者说是一种行为的集合
然后举了 Paginate
、Page
、Perpage
三者之间的关系,并推导出
这样的联系。
整个段落混淆了 泛型标记
和 实类型
,同时也引发了逻辑错误
对于
trait Paginate: Page + Perpage
根据大前提 trait 是 方法集合
,我们可以认为
同时,根据书中 第 68 页
代码清单 3-32:使用 trait 继承扩展功能
这里指出了 Paginate trait
中定义的 method
默认包含 Perpage trait
和 Page trait
中定义的 method
,也就是说
对于
都有
可推导出
与书中的表述是不相符的。
因此
trait Paginate: Page + Perpage
中的 加号 表示的是 并集
同理
impl<T: A + B> C for T { . . . }
fn<T: A + B> foo(bar: T) { . . . }
若把 T
、A
、B
都看成是 以某些方法为元素的集合
,
加号 依旧表示 并集
A + B
表示的是 trait A
和 trait B
中所有方法的总集合
对于 泛型标记 T
来说,冒号
表示 T
可以使用冒号后的 trait(s)
所具有的所有方法
对于 某个实类型 CT
来说,冒号
才是对它的限制:要求 CT
至少实现冒号后 trait(s)
定义的所有的方法才可以被当作泛型标记 T
来使用
若一个 实类型 CT
的确满足了泛型标记 T
的要求,
还将 实类型 CT
,泛型标记 T
,trait A
,trait B
都看作 以某些方法为元素的集合
的话,有下述关系
这里与本页中
“为所有
实现 Trait C”
也不相符
Rust 编程的哲学是组合优于继承
这段中
所以 Rust 中的类型可以看作语言允许的最小集合,不能再包含其他子集。而 trait 限定可以对这些类型集合进行组合,也就是求交集
这两句话在数学逻辑上也是冲突的:交集
产生的就是原集合的 子集
,与 不能再包含其他子集
冲突
对于 实类型
来说, trait bound
是一种具有 排除性质
的限定, trait bound
不关注 实类型
到底实现多少方法,只关注 实类型
没有实现的方法,一旦 实类型
没有实现 trait bound
中的全部方法,就标记该 实类型
不符合 trait bound
。
对于 泛型标记
来说, trait bound
是具有 累加性质
的限定,trait bound
不关注 泛型标记
实际使用了多少方法,只关注 泛型标记
已经使用的方法,一旦 泛型标记
使用了不存在于 trait bound
中的方法,就提示错误。
@eZioPan 感谢这么详细的反馈。我找时间确认下再讨论。
顺带纠正一处问题: trait 不是类型, trait object 才是。
@mzji trait是一个Unsize类型。Rust类型系统下,一切皆类型
trait 不是类型。证据:你不能创建 trait 的实例,但是你可以创建 trait object 。
更严谨的说法是,对于满足 object safety 的 trait 来说,每一个这样的 trait 都有一个对应的类型 dyn trait ,这个类型的实例就是这个 trait 所对应的 trait object 。
@eZioPan 刚才仔细看了你的内容,你说的没问题。其实我在写的时候,也是想表达类似的意思,但是不知道为啥把并集搞错成了交集。 可能我脑子里想的是 T 和 T: Trait交集是Trait。
感谢你的澄清。
如果把trait 认为是类型的集合 Type:TraitA + TraitB 是不是可以看作 Type 属于 集合(TraitA) 和 集合(TraitB)的交集?
对于
实类型
来说,trait bound
是一种具有排除性质
的限定,trait bound
不关注实类型
到底实现多少方法,只关注实类型
没有实现的方法,一旦实类型
没有实现trait bound
中的全部方法,就标记该实类型
不符合trait bound
检查bound的时候,只需要检查有没有impl这个trait就好了,不用逐个检查方法有没有实现。impl的时候才会检查有没有实现全。
@ZhangHanDong @angelrain1 我想两位说的应该是近似的意思了。本质上我们都是对同一个内容的不同描述方法,都没错。我的描述中将加法解释为并集比较符合数学上的直觉,而将冒号解释为限制是来源于冒号在 Rust 通常用法下的衍生:对变量的类型的限制。
这里我也建议张老师在这段体现一下 实类型
和 泛型标记
之间的差别,直接使用 T
和 T:Trait(s)
可能太容易让人混淆了。就像在描述函数的声明和调用的时候要区分 形式参数
(parameter
) 和 实际参数
(argument
) 一样,区分出来就更明确了。
@eZioPan 嗯,正在考虑如何修正内容。后续再讨论。
张老师可能是想讲解的通俗易懂,只是描述不够准确 "T 属于 A 与 B 的交集",这里的 A 、B 如果解释成是 impl A 的类型集合以及 impl B 的类型集合就没问题了。但是在之前 A B 都是值的 Trait,所以这句话模糊且多余,Trait bound 只关心 T 是否 impl A + B,并不检查 T 是否在 impl A 或者 impl B的集合中。只要解释清楚 Trait bound 是如何约束泛型、Trait,过多的解释可能并不能帮助读者理解问题~ 刚好又想到一点,说 supertrait 继承也不准确,trait A: B, impl A 必须 先impl B,两个 trait 实际是独立的,impl A 不会继承 B 的方法 感觉张老师喜欢用类比解释问题
@yim7 这个地方确实描述的不够精确。
其实我书里并没有说trait继承,是A会继承B的方法。trait用作限定,trait继承,可以理解为继承这种「行为的限定」。不过这部分内容,后面也会出一个修正,后续再继续讨论。
类比解释,只是写作的一种方式罢了,这个地方我感觉用数学集合解释比较有趣。
@eZioPan @yim7 @mzji
总结一下:
- trait可以看作是一种类型。毕竟trait bound也是参与类型检查的。
-
impl<T: A + B> C for T
,解释为,“为所有T⊂(impl A∩ impl B)实现Trait C”,也就是说,“为所有同时实现了A和B的类型T实现C”。这样不会引起歧义了。
对于trait方法来说,确实是并集。但我书里的内容,这不是重点。重点是被trait限定的类型。这些类型要求,即实现A,又实现B,那么就是交集了。
我来总结一下: 首先,这里问题出现的关键在于各位对于 A, B, T, C 这些集合的定义。
-
如果定义 A, B, C 为
T 即为其所有 Type 的话,那么 A, B, T, C 满足的关系应为
-
如果定义 A, B, C 为
T 即为其类型所有实现的 methods 的话,那么 A, B, T, C 满足的关系应为
我认为张老师可以再对描述润色一下,使其更严谨。 但需要指出的是,无论那种理解,第三章插图3-5中的画法都是错误的
@LEXUGE 感谢反馈,晚点再回复。
我写了一篇博客(英文),应该更加完整一些
@LEXUGE 赞
@LEXUGE 赞
其实你在书中 impl<T: A + B> C for T
这句语句是很难用维恩图或者集合来描述的,因为C 和 T都很具有任意性。这样就给描述造成困难。
我觉得我在博客里总结的两条inference应该还是能算是简单推导出来的,如果要描述你的那句语句的话可能就要各种分类讨论(比如T有没有实现A/B/C的sub/supertrait),以我的能力感觉有困难。
@LEXUGE 我在书里的图,只是想给读者一种启迪的作用,方便大家理解trait。
@LEXUGE 我在书里的图,只是想给读者一种启迪的作用,方便大家理解trait。
原来是这样啊...那我看书时有点较真了
@LEXUGE 没事,这样挺好,可以帮助我提升准确度。
能否把把电子版也修改一下呢,也是花了钱买的
@gm3000 我知道你是花了钱买的,但是电子版也不是我能直接控制的。 出版社需要修订,修订完还得交给售卖平台上架,这里面也有很多工序。我只能催促出版社,出版社再催促平台,如果我能直接控制,我早修改了。
在看到这段时,还以为我一直把这个关系理解错了,一开始以为是Rust的特色。又去验证了一下,又查了一下,看到此页,原来是作者笔误。