28、泛型和关联类型
泛型和关联类型
首先重新复习一下之前学到的两个关于trait的定义,From
和Deref
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只能Deref
到str
。这是为了避免歧义:如果我们可以为一个类型实现多次Deref
,那么当我们调用&Self
方法时,编译器就不知道应该选择哪种Target
类型。
这就是Deref
使用关联类型Target
的原因。关联类型由trait实现
唯一确定。因为我们不能多次实现Deref
,因此我们只能为给定类型指定一个Target,并不会有任何歧义。
通用trait(泛型)
另一方面,我们可以为一个类型多次实现From trait
,只要输入类型T
是不同的。比如说,我们可以使用u32
和u16
作为输入类型来为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)
}
}
不得不吐槽一下,这个题真是让人搞不明白,测试用例是
我一开始以为要对多少类型实现这个方法呢,应该是泛型,.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,你这真就是完全比着测试样例来啊,既然你比着测试样例来,凭啥不让我看测试样例。上面那个测试样例的图片实际上是在不通过的时候屏幕闪现的,我刷一下给它截了,不然都看不到测试样例,这辈子都不知道这道题怎么完成,题目描述感觉也不太清晰。
唉,说到底还是技术不行,还是那句话,人不行别怪路不平,继续努力吧…