关联类型和相关类型
让我们重新审视目前为止学习过的两个特性From
和Deref
的定义:
#![allow(unused)] fn main() { trait From<T> { fn from(value: T) -> Self;} trait Deref { type Target; fn deref(&self) -> &Self::Target;} }
它们都涉及到类型。在From
的情况下,它是泛型参数T
。在Deref
的情况下,它是相关类型Target
。
有什么不同?为什么要用一个而非另一个?
最多实现
由于deref强制转换的工作原理,给定类型只能有一个"目标"类型"。例如,String
只能Deref
到str
。这是为了避免歧义:如果你能多次实现Deref
,当调用self
方法时编译器应该选择哪个Target
?
这就是Deref
使用相关类型的原因,Target
。相关类型是由特性实现唯一确定的。因为你不能实现Deref
超过一次,你只能为给定类型指定一个Target
,不会有歧义。
泛型特性
另一方面,你可以为类型多次实现From
,**只要输入类型不同即可。例如,你可以用
u32和
u16作为输入类型为
WrappingU32实现
From`:
#![allow(unused)] fn main() { impl From<u32> for WrappingU32 { fn from(value: u32) -> Self { WrappingU32 { inner: value } impl<u16> for WrappingU32 { fn from(value: u16) -> Self { WrappingU32 { inner: value.into()} }
这可行,因为From<u16>
和From<u32>
被视为不同特性。没有歧义:编译器可以根据转换值的类型决定使用哪个实现。
案案例分析:Add
作为结束示例,考虑标准库中的Add
特性:
#![allow(unused)] fn main() { trait Add<RHS = Self> { type Output; fn add(self, rhs: RHS) -> Self::Output;} }
它使用了两种机制:
- 有一个泛型参数
RHS
(右手边),默认为Self
- 有一个相关类型
Output
,加法的结果类型
RHS
RHS
是一个泛型,允许不同类型相加在一起。例如,在标准库中你会发现这两个实现:
#![allow(unused)] fn main() { impl<u32> for u32 { type Output = u32; fn add(self, u32) -> u32 { [...]} impl<&u32> for u32 { type Output = u32; fn add(self, &u32) -> u32 { [...]} }
这让下面的代码可以编译:
#![allow(unused)] fn main() { let x = 5u32 + &5u32 + 6u32 }
因为u32
实现在实现了Add<&u32>
和以及
Add
Output
另一方面,必须在操作数类型已知时唯一确定。这就是它作为相关类型而不是第二个泛型参数的原因。
总结:
- 当类型必须为给定实现时使用相关类型。
- 当想允许同一类型有**多个实现时使用泛型,输入类型不同。
参考资料
- 本节的练习位于
exercises/04_traits/09_assoc_vs_generic