32、Traits总练习

题目

  • 定义一个SaturatingU16类型。
  • 该类型有一个u16的值。
  • 它支持从u16u8&u16&u8转换。
  • 它支持加法运算符重载。
  • 确保SaturatingU16u16&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可以从从u16u8&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
    }
}

同时还要实现SaturatingU16SaturatingU16类型进行比较,这个只需要在pub struct上面添加#[derive(PartialEq)]即可。

5、实现Debug打印

 

跟上面那个一样,添加#[derive(Debug,PartialEq)]即可

 

6.添加CloneCopy

如果现在执行代码会在这里报错

image-20250916141709855

image-20250916141716589

我们应该实现Clone。

 

只实现Clone也不行

image-20250916141840081

image-20250916141847939

依然会报错。

 

我们再实现Copy就好了

image-20250916141924362

这样,这个程序就算写好了,不过我现在还是搞不明白,为什么需要实现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,但无法再用。

 

好复杂呜呜呜~

这些留着等我以后没事了再多看几遍(

 

 

阅读剩余
THE END