关联类型和相关类型

让我们重新审视目前为止学习过的两个特性FromDeref的定义:

#![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只能Derefstr。这是为了避免歧义:如果你能多次实现Deref,当调用self方法时编译器应该选择哪个Target

这就是Deref使用相关类型的原因,Target。相关类型是由特性实现唯一确定的。因为你不能实现Deref超过一次,你只能为给定类型指定一个Target,不会有歧义。

泛型特性

另一方面,你可以为类型多次实现From,**只要输入类型不同即可。例如,你可以用u32u16作为输入类型为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