32、Traits总练习
题目
- 定义一个
SaturatingU16
类型。 - 该类型有一个u16的值。
- 它支持从
u16
,u8
,&u16
,&u8
转换。 - 它支持加法运算符重载。
- 确保
SaturatingU16
,u16
,&u16
和&SaturatingU16
加法达到u16::MAX
时饱和。 - 确保可以与另外一个
SaturatingU16
类型或u16
类型进行比较。 - 确保可以打印
Debug
输出形式。 - 可以通过以下测试
use task_traits_outro::SaturatingU16;
#[test]
fn test_saturating_u16() {
let a: SaturatingU16 = (&10u8).into();
let b: SaturatingU16 = 5u8.into();
let c: SaturatingU16 = u16::MAX.into();
let d: SaturatingU16 = (&1u16).into();
let e = &c;
assert_eq!(a + b, SaturatingU16::from(15u16));
assert_eq!(a + c, SaturatingU16::from(u16::MAX));
assert_eq!(a + d, SaturatingU16::from(11u16));
assert_eq!(a + a, 20u16);
assert_eq!(a + 5u16, 15u16);
assert_eq!(a + e, SaturatingU16::from(u16::MAX));
}
编码
分析题目,我们要创建一个SaturatingU16
类型,并实现6个特性,还要通过测试。
1、首先定义类型
pub struct SaturatingU16{
value:u16,
}
2、实现From功能
SaturatingU16
可以从从u16
,u8
,&u16
,&u8
转换。所以我们逐个为它实现这些方法。
用到了第二十七节的知识点,代码编写格式如下
impl From<从什么类型转换> for <转换为什么类型>{
fn from(value:<从什么类型转换>) -> <转换为什么类型> {
SaturatingU16 { value }
}
}
这一块之前没学好,这次专门认真看了看。
第二个要求可以编写四个From trait
的实现:
impl From<u16> for SaturatingU16 {
fn from(value: u16) -> SaturatingU16 {
SaturatingU16 { value }
}
}
impl From<u8> for SaturatingU16 {
fn from(value: u8) -> Self {
let value = value as u16;
SaturatingU16 { value }
}
}
impl From<&u16> for SaturatingU16 {
fn from(value: &u16) -> Self {
let value = *value;
SaturatingU16 { value }
}
}
impl From<&u8> for SaturatingU16 {
fn from(value: &u8) -> Self {
let value = *value as u16;
SaturatingU16 { value }
}
}
3、实现加法运算重载
要实现加法运算的重载,需要鼓捣Add trait
。
为什么要实现Add trait
,这其实都是Rust官方规定好的,这一切都是trait罢了,从官方文档查找所有运算符的trait。
所以这里我们实现Add trait
:
impl std::ops::Add for SaturatingU16 {
type Output = SaturatingU16;
fn add(self, rhs: Self) -> Self::Output {
SaturatingU16 { value: &self.value + rhs.value }
}
}
还是不太清楚,为什么要用type Output = SaturatingU16;
这行代码?
这是关联类型那一节的内容,到底什么是关联类型?为什么要在这里用关联类型?
关联类型 是一种 “与 trait 相关的类型”,它让 trait 可以在不固定返回或参数类型的前提下,动态绑定一个具体类型名称。
Add
是一个通用trait,如果不用type Output=SaturatingU16;
,那么Rust就无法知道a1.add(a2)
或a1+a2
的结果是什么类型,也不知道如何做链式调用,也不知道如何被泛型函数使用。
按照这里的编码形式,首先实现SaturatingU16 + SaturatingU16
:
impl std::ops::Add for SaturatingU16 {
type Output = SaturatingU16;//使用关联类型,将SaturatingU16关联给Output
//左操作数是self,省略掉,右操作数也是Self
fn add(self, rhs: Self) -> Self::Output {
self + rhs.value
}
}
所有的运算符trait
都在std::ops
里面。
Self::Output
关联类型引用的是 impl
块中定义的 type Output
。也就等同于了SaturatingU16
。
然后为剩下几个assert_eq!
实现对应的Add trait
。
impl std::ops::Add for SaturatingU16 {
type Output = SaturatingU16;
fn add(self, rhs: Self) -> Self::Output {
self + rhs.value
}
}
//std::ops::Add<右操作数的类型>
impl std::ops::Add<&SaturatingU16> for SaturatingU16 {
type Output = SaturatingU16;
fn add(self, rhs: &SaturatingU16) -> Self::Output {
//这里其实直接调用的
//impl std::ops::Add for SaturatingU16{}
//这个实现
self + *rhs
}
}
impl std::ops::Add<u16> for SaturatingU16 {
type Output = Self;
//Output是Self
//在这里Self就是SaturatingU16
//为谁实现的trait,谁就是这段代码块的Self
//不管怎样,运算符重载的返回值都必须是Self::<关联类型>
//一般都写成Self::Output
fn add(self, rhs: u16) -> Self::Output {
let sum = self.value.saturating_add(rhs);
Self { value: sum }
}
}
impl std::ops::Add<&u16> for SaturatingU16 {
type Output = SaturatingU16;
fn add(self, rhs: &u16) -> Self::Output {
//这里其实调用的是上面的
//impl std::ops::Add<u16> for SaturatingU16{}
//这个实现
self + *rhs
}
}
4、和SaturatingU16
类型或u16
类型进行比较
这里要实现PartialEq trait
,就像Add trait
那样。
impl PartialEq<u16> for SaturatingU16 {
fn eq(&self, other: &u16) -> bool {
self.value == *other
}
}
同时还要实现SaturatingU16
与SaturatingU16
类型进行比较,这个只需要在pub struct
上面添加#[derive(PartialEq)]
即可。
5、实现Debug打印
跟上面那个一样,添加#[derive(Debug,PartialEq)]
即可
6.添加Clone
和Copy
如果现在执行代码会在这里报错
我们应该实现Clone。
只实现Clone也不行
依然会报错。
我们再实现Copy就好了
这样,这个程序就算写好了,不过我现在还是搞不明白,为什么需要实现Clone和Copy呢?
*rhs
解引用为SaturatingU16
,此时要执行self + SaturatingU16
。- 这个加法必须通过
impl Add for SaturatingU16
。 - 但是
self
是移动过来的值,不能再次使用。 - 如果
SaturatingU16
没有实现Copy
,那么self
在函数内部已经被 move; 同时,*rhs
被解引用后是一个SaturatingU16
值。 - 而
Add
trait 的add
方法要求self
是一个不可变引用或值,且不能被移动两次。 - 更严重的是:
self
是传入的值,不能复制 → 所以self + *rhs
使用了已 move 的self
,但无法再用。
好复杂呜呜呜~
这些留着等我以后没事了再多看几遍(