28、泛型和关联类型

泛型和关联类型

首先重新复习一下之前学到的两个关于trait的定义,FromDeref

pub trait From<T> {
    fn from(value: T) -> Self;
}

pub trait Deref {
    type Target;
    
    fn deref(&self) -> &Self::Target;
}

他们都有类型参数。

对于From,他是一个通用参数T

对于Deref,他是一个关联类型Target

这一节研究的是这两个的区别。

最多只有一个实现

由于deref强制的工作模式,给定的类型只能有一个Target类型。

例如:String只能Derefstr。这是为了避免歧义:如果我们可以为一个类型实现多次Deref,那么当我们调用&Self方法时,编译器就不知道应该选择哪种Target类型。

 

这就是Deref使用关联类型Target的原因。关联类型由trait实现唯一确定。因为我们不能多次实现Deref,因此我们只能为给定类型指定一个Target,并不会有任何歧义。

通用trait(泛型)

另一方面,我们可以为一个类型多次实现From trait,只要输入类型T是不同的。比如说,我们可以使用u32u16作为输入类型来为WarppingU32实现From

//实现from(u32)可以转换为WarppingU32
impl From<u32> for WrappingU32 {
    fn from(value: u32) -> Self {
        WrappingU32 { inner: value }
    }
}

//实现from(u16)可以转换为WarppingU32
impl From<u16> for WrappingU32 {
    fn from(value: u16) -> Self {
        WrappingU32 { inner: value.into() }
    }
}

这段代码是正常的,因为From<u16>From<u32>被视为两种不同的类型。

这里并不存在歧义:编译器可以根据要转换的值的类型确定使用哪种实现。

案例研究:Add

最后一个例子是标准库的Add trait。

pub trait Add<RHS = Self> {
    type Output;
    
    fn add(self, rhs: RHS) -> Self::Output;
}

它使用两种机制:

  • 他有一个通用参数RHS,默认为Self
  • 他有一个关联类型,Output也就是加法的结果的类型。

RHS

RHS是一个通用参数,它允许将不同类型的数据添加到一起。

例如,我们可以从标准库中找到这两种实现:

impl Add<u32> for u32 {
    type Output = u32;
    
    fn add(self, rhs: u32) -> u32 {
      //                      ^^^
      // This could be written as `Self::Output` instead.
      //The compiler doesn't care, as long as the type you specify         //here matches the type you assigned to `Output` right above.
      //也可以写成 `Self::Output`。
      //编译器并不关心,只要你在这里指定的类型与上面分配给 `Output` 的类型一致即	   //可。
      // [...]
    }
}

impl Add<&u32> for u32 {
    type Output = u32;
    
    fn add(self, rhs: &u32) -> u32 {
        // [...]
    }
}

 

这就可以编译下面的代码了

let x = 5u32 + &5u32 + 6u32;

因为 u32 实现了 Add<&u32> 以及 Add<u32>

Output

Output表示加法运算结果的类型。

 

为什么我们首先需要Output?能拿到我们就不能使用实现Add的类型Self作为输出吗?我们可以这样做,但这会限制限制trait的灵活性。比如,我们可以在标准库中找到以下的实现:

impl Add<&u32> for &u32 {
    type Output = u32;

    fn add(self, rhs: &u32) -> u32 {
        // [...]
    }
}

他们实现该特性的类型是 &u32,但加法的结果却是 u32。

 

如果add必须返回Self(在上面的例子中是u32),那么就不可能提供这种实现。Output 使得标准库能够将实现者与返回类型解耦,从而支持这种情形。

另一方面,Output不是泛型参数。一旦操作数的类型已知,操作的输出类型必须唯一确定。这就是为什么它是一种关联类型:对于给定的实现器和泛型参数的组合,只有一种Output类型。

结论

  • 当必须唯一确定给定trait实现的类型时,应该使用关联类型。
  • 如果要允许同一类型的trait有多个实现,但输入类型不同,则使用泛型参数。

练习

// TODO: Define a new trait, `Power`, that has a method `power` that raises `self`
//  to the power of `n`.
//  The trait definition and its implementations should be enough to get
//  the tests to compile and pass.
//  定义一个新的 trait Power,该 trait 拥有一个方法 power,
//  用于将 self 提升到幂次 n。trait 的定义及其实现应足以让测试通过编译并成功运行。
// Recommendation: you may be tempted to write a generic implementation to handle
// all cases at once. However, this is fairly complicated and requires the use of
// additional crates (i.e. `num-traits`).
// Even then, it might be preferable to use a simple macro instead to avoid
// the complexity of a highly generic implementation. Check out the
// "Little book of Rust macros" (https://veykril.github.io/tlborm/) if you're
// interested in learning more about it.
// You don't have to though: it's perfectly okay to write three separate
// implementations manually. Venture further only if you're curious.

//定义泛型
pub trait Power<T> {
    type U;//定义类型U

    //左边参数是调用者,右边参数是.power()里面的值,返回值是U类型
    fn power(&self, n: T) -> Self::U;
}

//为u32类型实现.power(value:u16)的方法,返回值是u32
impl Power<u16> for u32 {
    type U = u32;

    fn power(&self, n: u16) -> Self::U {
        self.pow(n as u32)
    }
}

//为u32类型实现.power(value:&u32)的方法,返回值是u32
impl Power<&u32> for u32 {
    type U = u32;

    fn power(&self, n: &u32) -> Self::U {
        self.power(*n)
    }
}

//为u32类型实现.power(value:u32)的方法,返回值是u32
impl Power<u32> for u32 {
    type U = u32;

    fn power(&self, n: u32) -> Self::U {
        self.pow(n)
    }
}

不得不吐槽一下,这个题真是让人搞不明白,测试用例是

image-20250915103218547

我一开始以为要对多少类型实现这个方法呢,应该是泛型,.power()里面的参数可能是任意的多种类型,怎么想也不知道代码怎么写,最后看了一下题解:

pub trait Power<Exponent = Self> {
    type Output;

    fn power(&self, n: Exponent) -> Self::Output;
}

impl Power<u16> for u32 {
    type Output = u32;

    fn power(&self, n: u16) -> Self::Output {
        self.pow(n.into())
    }
}

impl Power<&u32> for u32 {
    type Output = u32;

    fn power(&self, n: &u32) -> Self::Output {
        self.power(*n)
    }
}

impl Power<u32> for u32 {
    type Output = u32;

    fn power(&self, n: u32) -> Self::Output {
        self.pow(n)
    }
}

nnd,你这真就是完全比着测试样例来啊,既然你比着测试样例来,凭啥不让我看测试样例。上面那个测试样例的图片实际上是在不通过的时候屏幕闪现的,我刷一下给它截了,不然都看不到测试样例,这辈子都不知道这道题怎么完成,题目描述感觉也不太清晰。

 

唉,说到底还是技术不行,还是那句话,人不行别怪路不平,继续努力吧…

 

阅读剩余
THE END