rust trait

Created

2024-05-13 20:53:47

Updated

2024-10-27 13:18:30

1 定义

Tip
  • trait在其他语言里,类似接口interface
  • trait里定义若干行为或者说功能(在代码中体现为方法)
    • 是一种约束
      • 类型是对变量申请的内存空间的一种约束
      • 泛型可以说就是因为类型约束的过于严格而产生的
      • 然后泛型又过于宽泛了,啥都行,我就想要符合某种特征(具有某些行为)的类型,这样就产生了trait用来约束泛型
    • 只要实现某个trait
      • 就具备了某种能力或功能
      • 共享了一些能力: trait 定义里有默认实现的方法,所有实现该trait的类型都可以直接用

2 入门例子

Important
  • 我们在结构体章节中已经提到, self 相当于 self: Self, Self是一种类型,表示结构体本身
  • trait中定义的方法里也是一样的省略写法
    • Self 表示实现了该trait的具体类型
    • self = self: Self
    • &self = self: &Self
    • &mut self = self: &mut Self
    • 如果trait中定义的方法的第一个参数是以上三种(还有类似这种参数设置 self: Box<Self>), 那么表示这是一个实例方法, 其他情况则称为关联方法(或者称:静态方法)
trait ExampleTrait {
    // 没有方法体的方法, 实现该trait的类型 必须实现该方法
    // 使用 &self 参数的方法,不修改数据
    fn inspect(&self) -> &str;

    // 使用 &mut self 参数的方法,可以修改数据
    fn modify(&mut self, tmp: &str);

    // 使用 self 参数的方法,通常用于消费 trait 对象本身
    fn consume(self);
    // 有默认实现的方法, 实现该trait的类型可以不用写
    // 当然也可以重新写来覆盖这个方法的具体实现
    // 可以说是一种代码共享了, 因为可以直接用了
    fn say(&self) {
        println!("hello rust");
    }
    // 无接收者的静态方法
    fn hello();
    // 不是self:&Self, 用其他的名字看看
    // 有默认实现
    fn world(this: &Self) {
        println!("use this");
    }
}

struct ExampleStruct {
    value: String,
}

// 为ExampleStruct实现 ExampleTrait
impl ExampleTrait for ExampleStruct {
    fn inspect(&self) -> &str {
        &self.value
    }
    fn modify(&mut self, tmp: &str) {
        self.value.push_str(tmp);
    }
    fn consume(self) {
        println!("Consumed value: {}", self.value);
        // `self` 被消费,之后无法使用
    }
    // 必须实现这个
    fn hello(){
        println!("hello example");
    }
}

fn main() {
    let mut example = ExampleStruct {
        value: "Hello".to_string(),
    };
    // 直接调用trait里的已经实现了的方法
    example.say();

    ExampleStruct::hello(); // 调用静态函数
    // 第一个参数名不是 self, 用其他, 只能用这种方式, 不能用 实例.方法()
    ExampleTrait::world(&example); // 调用trait 有默认实现的静态方法

    // 调用 inspect 方法, 相当于 ExampleTrait::inspect(&example)
    println!("Before modification: {}", example.inspect());
    println!("Before modification: {}", ExampleTrait::inspect(&example));

    // 调用 modify 方法
    example.modify(" world");

    // 调用 inspect 方法,查看修改结果
    println!("After modification: {}", example.inspect());
    // 相当于
    ExampleTrait::modify(&mut example, " rust");
    println!("After modification: {}", example.inspect());

    // 调用 consume 方法,这会消费 example,之后不能再使用 example
    example.consume();
    // 下面这行代码如果取消注释会导致编译错误,因为 example 已经被消费了
    // println!("Cannot use example after consume: {}", example.inspect());
}

trait方法第一个参数的变量名是self, 但是类型是这种Box<Self> 的情况

trait ExampleTrait {
    fn consume(self: Box<Self>);
}

struct ExampleStruct {
    value: String,
}

impl ExampleTrait for ExampleStruct {
    fn consume(self: Box<Self>) {
        println!("Consumed value: {}", (*self).value);
        println!("Consumed value: {}", self.value);
    }
}
fn main() {
    let example = Box::new(ExampleStruct {
        value: "Hello".to_string(),
    });

    example.consume();
}

3 给内置或外部类型实现trait

3.1 为i32 实现一个我们定义的trait

trait Pow {
    fn pow(&self) -> i32;
}
impl Pow for i32 {
    fn pow(&self) -> i32 {
        *self * *self
    }
}
fn main() {
    let a = 3;
    println!("{}", a.pow());
}

3.2 孤儿规则 (orphan rule)

Caution
  1. 如果你想要为类型A实现trait B,那么要么类型A是在当前的crate中定义的,要么trait B是在当前的crate中定义的
  2. 如果要实现外部定义的trait需要先将其导入作用域
  3. 再换句话说就是
    1. 可以对外部类型实现自定义的trait
    2. 可以对自定义类型上实现外部trait
    3. 外部是指不是由当前crate,而是由外部定义的,比如标准库

3.3 newtype”绕过”孤儿规则

use std::fmt;

// only traits defined in the current crate can be implemented for types defined outside of the crate
// define and implement a trait or new type instead
// impl fmt::Display for Vec<String> {
//     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
//         write!(f, "[{}]", self.0.join(", "))
//     }
// }
// 使用元祖结构体 变成一个新的类型,是本地的
// Vec 是标准库里的,属于外部类型
struct Wrapper(Vec<String>);
// fmt::Display 这个是外部trait
impl fmt::Display for Wrapper {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "[{}]", self.0.join(", "))
    }
}

fn main() {
    let w = Wrapper(vec![String::from("hello"), String::from("world")]);
    println!("w = {}", w);
}

4 完全限定语法

Important
  • 当一个结构体实现了多个trait,而这些trait里有相同的方法,无法直接用实例.方法()来调用,那么如何调用这些方法呢?
// 定义第一个trait,它有一个名为`announce`的方法
trait TraitOne {
    fn announce(&self);
}

// 定义第二个trait,它也有一个名为`announce`的方法
trait TraitTwo {
    fn announce(&self);
}

// 定义一个结构体`Speaker`
struct Speaker {
    message: String,
}

// 为`Speaker`实现`TraitOne`
impl TraitOne for Speaker {
    fn announce(&self) {
        println!("traitOne: {}", &self.message)
    }
}

// 为`Speaker`实现`TraitTwo`
impl TraitTwo for Speaker {
    fn announce(&self) {
        println!("traitTwo: {}", &self.message)
    }
}

fn main() {
    // 创建一个`Speaker`实例
    let speaker = Speaker {
        message: "Hello".to_string(),
    };

    // speaker.announce(); 报错了 提示用多个announce
    // 使用完全限定语法调用`TraitOne`的`announce`方法
    <Speaker as TraitOne>::announce(&speaker);
    TraitOne::announce(&speaker);

    // 使用完全限定语法调用`TraitTwo`的`announce`方法
    <Speaker as TraitTwo>::announce(&speaker);
    TraitTwo::announce(&speaker);
}

5 trait 作为约束

5.1 impl 写法

参数item, 必须是实现了Summary这个trait的类型,这里是该类型的可变借用

pub fn notify(item: &impl Summary) {
    println!("Breaking news! {}", item.summarize());
}

5.2 泛型 写法

impl Summary 相比较这个写法, 我们一般用下面这样的写法

pub fn notify<T: Summary>(item: &T) {
    println!("Breaking news! {}", item.summarize());
}

pub fn notify(item1: &impl Summary, item2: &impl Summary) {
    // ...
}
// 多个参数, 这样写就比上面的简洁
pub fn notify<T: Summary>(item1: &T, item2: &T) {
    // ...
}
指定多个trait
pub fn notify(item: &(impl Summary + Display)) {
    // ...
}

pub fn notify<T: Summary + Display>(item: &T) {
    // ...
}

5.3 where 写法

当我们在指定多个参数 然后每个参数要实现多个trait时, 写起来可能不是那么清晰, 这时我们可以使用where来简化

fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {
    //...
}
使用where的写法
fn some_function<T, U>(t: &T, u: &U) -> i32
where
    T: Display + Clone,
    U: Clone + Debug,
{
    // ...
}

6 trait的不同形式

6.1 简单 trait

入门例子中举的例子就属于非常简单的类型

trait ExampleTrait {
    fn inspect(&self) -> &str;
}

6.2 泛型 trait

6.2.1 产生原因和使用方式

不使用泛型的情况
#[derive(Debug)]
struct Number {
    value: i32,
}
// 定义一个trait
trait MyFrom {
    // 这个"行为"的想法是 想从类型i32,生成一个类型 Self (即: 实现该trait的主体)
    fn my_from(item: i32) -> Self;
}
impl MyFrom for Number {
    fn my_from(item: i32) -> Self {
        Self { value: item }
    }
}
#[derive(Debug)]
struct MyString {
    value: String,
}
trait MyFrom2 {
    // 由于类型不同, 我们不得不 重新定义个 trait
    // 里面的"行为" 实际它的逻辑一样, 都是从一个类型生成 Self 类型
    // 但是我们不得不重新写一遍
    fn my_from(item: String) -> Self;
}
impl MyFrom2 for MyString {
    fn my_from(item: String) -> Self {
        Self { value: item }
    }
}
fn main() {
    let num = Number::my_from(30);
    let str2 = MyString::my_from("hello".to_string());
    println!("My number is {:?}", num);
    println!("My string is {:?}", str2);
}
  • 可以从一种类型生成另外一种类型, 这个行为应该是非常抽象的, 是说几乎每个类型都可以有这样一个行为
使用泛型参数的trait
#[derive(Debug)]
struct Number {
    value: i32,
}
// trait 定义的行为 依赖于多个不同类型, 我们就使用泛型
trait MyFrom<T> {
    fn my_from(item: T) -> Self;
}
impl MyFrom<i32> for Number {
    fn my_from(item: i32) -> Self {
        Number { value: item }
    }
}
#[derive(Debug)]
struct MyString {
    value: String,
}
impl MyFrom<String> for MyString {
    fn my_from(item: String) -> Self {
        Self { value: item }
    }
}

fn main() {
    let num = Number::my_from(30);
    let str2 = MyString::my_from("hello".to_string());
    println!("My number is {:?}", num);
    println!("My string is {:?}", str2);
}
use std::fmt::Display;

trait Animal<T>
where
    T: Display,
{
    fn shout(&self, v: T);
}
struct Dog;

// impl<T> 中的<T> 是申明泛型
// Aniaml<T> 中的T 就已经是在使用了,不是申明
impl<T> Animal<T> for Dog
where
    T: Display,
{
    fn shout(&self, v: T) {
        println!("{}", v);
    }
}
// 这里会报错, 因为 前面 的  `impl<T> Animal<T> for Dog` 已经包含了这里的情况
// 如果你 上面的不要写, 单独为Dog 实现多个 trait, `impl Animal<String>` ,`impl Animal<i32>`这样具化类型, 是可以的
// impl Animal<String> for Dog {
//     fn shout(&self, v: String) {
//         println!("{}", v);
//     }
// }

fn main() {
    let a = Dog;
    // Dog 的实例实现了 trait (Animal<T>)的shout 方法
    // shout 使用的参数类型, 具化了 T
    // 下面2个都ok
    a.shout(123);
    a.shout("wang wang");
}

6.2.2 设置泛型默认值

trait Summable<T = i32> {
    fn sum(&self) -> T;
}

// 实现Summable trait,使用默认的泛型类型 i32
impl Summable for Vec<i32> {
    fn sum(&self) -> i32 {
        self.iter().sum()
    }
}

// 实现Summable trait,但为泛型指定一个不同的类型 f64
impl Summable<f64> for Vec<f64> {
    fn sum(&self) -> f64 {
        self.iter().sum()
    }
}

fn main() {
    let vec_i32: Vec<i32> = vec![1, 2, 3];
    let vec_f64: Vec<f64> = vec![1.0, 2.0, 3.0];

    // 使用默认的泛型类型 i32
    println!("Sum of vec_i32: {}", vec_i32.sum());

    // 使用指定的泛型类型 f64
    println!("Sum of vec_f64: {}", vec_f64.sum());
}

6.3 关联类型 trait

6.3.1 产生的原因和使用

// 有一个trait, 需要多个泛型参数
trait ExampleTrait<A, B, C> {
    fn tmp(&self, x: A, y: B) -> C;
    fn get(&self) -> C;
}
struct ExampleStruct {}
// 每次你为某个结构体实现这个trait时, 就需要写 很多个泛型参数
// 很麻烦
impl ExampleTrait<i32, i64, i8> for ExampleStruct {
    fn tmp(&self, x: i32, y: i64) -> i8 {
        6i8
    }
    fn get(&self) -> i8 {
        self.tmp(1, 2)
    }
}
// 当你写一个函数, 参数类型需要 实现ExampleTrait 的时候
// 你需要这样写上一堆 泛型参数, 也很麻烦
fn test<A, B, C, D>(x: &D) -> C
where
    D: ExampleTrait<A, B, C>,
{
    x.get()
}
使用关联类型
trait ExampleTrait {
    type A; // 像占位符一样
    type B;
    type C;
    fn tmp(&self, x: Self::A, y: Self::B) -> Self::C;
    fn get(&self) -> Self::C;
}
struct ExampleStruct1 {}
impl ExampleTrait for ExampleStruct1 {
    // 给具体类型实现 trait的时候
    // 我们就写成具体的类型
    type A = i32;
    type B = i64;
    type C = i8;
    // 里面可以直接写 具体的类型
    fn tmp(&self, x: i32, y: i64) -> i8 {
        // ... 这里无需关心具体实现.. 我们就只是说明关联类型的优点
        6i8
    }
    fn get(&self) -> i8 {
        self.tmp(1, 2)
    }
}
struct ExampleStruct2 {}
impl ExampleTrait for ExampleStruct2 {
    type A = i32;
    type B = i64;
    type C = i8;
    // 当然可以写 Self::A 这种 (和前面写具体类型都是可以的)
    fn tmp(&self, x: Self::A, y: Self::B) -> Self::C {
        1i8
    }
    fn get(&self) -> Self::C {
        self.tmp(1, 2)
    }
}
// 有一个函数, 参数类型需要 实现ExampleTrait2
fn test<D>(x: &D) -> D::C
where
    D: ExampleTrait,
{
    x.get()
}
fn main() {
    let a = ExampleStruct1 {};
    let f = test(&a);
    println!("{}", f);
    let b = ExampleStruct2 {};
    let f = test(&b);
    println!("{}", f);
}

6.3.2 使用trait的关联类型来约束类型

trait Person {
    type AAA;
    fn shout(&self, msg: Self::AAA);
}
// 定义一个结构体,规定内部成员homeowner的类型必须实现Person这个trait
// 成员 house_name 的类型 必须是 实现了Person 这个trait的类 中定义的关联类型 AAA
struct House<T: Person> {
    homeowner: T,
    house_name: T::AAA,
}

struct Man {
    name: String,
}
impl Person for Man {
    // 如果将这个 AAA 改成其他类型, 那么就会在实例化 House时house_name 也要改成这里指定的类型
    type AAA = String;
    fn shout(&self, msg: Self::AAA) {
        println!("{}", msg);
    }
}
fn main() {
    let s = House {
        homeowner: Man {
            name: "jerry".to_string(),
        },
        house_name: "jerry's home".to_string(),
    };
    s.homeowner.shout("啊啊啊".to_string());
}

6.3.3 直接指定具体类型来约束关联类型

trait Person {
    type AAA;
    fn shout(&self, msg: Self::AAA);
}
// 定义一个结构体
//  1. 约束了内部成员的类型必须实现Person这个trait
//  2. Person实现这个trait的代码中使用的关联类型必须是String
struct House<T: Person<AAA = String>> {
    homeowner: T,
}

struct Man {
    name: String,
}
// 为了实例化 House 中使用的字段 homeowner 可以用
// 我们为其实现Person trait
impl Person for Man {
    // 关联类型 设置为String
    // 如果将这个 AAA 改成其他类型, 那么就会在实例化 House 那里报错
    // 因为这样的 Man 不是我们要的
    type AAA = String;
    fn shout(&self, msg: Self::AAA) {
        println!("{}", msg);
    }
}
fn main() {
    let s = House {
        homeowner: Man {
            name: "jerry".to_string(),
        },
    };
    s.homeowner.shout("啊啊啊".to_string());
}

6.3.4 使用trait 约束关联类型

use std::fmt::Display;

trait Person {
    type AAA: Display;
    fn shout(&self, msg: Self::AAA);
}
struct House<T: Person> {
    homeowner: T,
}

struct Man {
    name: String,
}
impl Person for Man {
    type AAA = i32; //String 也行, 必须是实现了 Display 这个trait
    fn shout(& self, msg: Self::AAA) {
        println!("{}", msg);
    }
}
fn main() {
    let  s = House {
        homeowner: Man {
            name: "jerry".to_string(),
        },
    };
    s.homeowner.shout(123);
}

6.4 关联常量 trait

trait Country {
    const HISTORY: u32 = 5000;
    const INDEPENDENT: bool = true;
    fn get_code(&self) -> u32 {
        Self::HISTORY
    }
}

struct China;
impl Country for China {
    const HISTORY: u32 = 7000;
}

fn main() {
    let c = China;
    println!("{}", c.get_code());
    println!("{:?}", China::HISTORY);
    println!("{:?}", <China as Country>::HISTORY);
    println!("{:?}", China::INDEPENDENT);
    println!("{:?}", <China as Country>::INDEPENDENT);
}

6.5 继承 trait

Caution
  • 用继承这个词, 其实我觉得不太好
  • 比如如果说trait A继承了B, 但是实际A并没有B的行为, 只是你在实现trait A的时候,必须实现trait B
trait Animal {
    fn shout(&self);
}
// 表示如果有类型要实现 Person 这个 trait, 那么它必须也要实现 Animal这个trait
// 相当于 T: Person + Animal
trait Person: Animal {
    fn speak(&self);
    fn shout(&self);
}

struct Student;

impl Person for Student {
    fn speak(&self) {
        println!("hello...");
    }
    fn shout(&self) {
        println!("person...");
    }
}
impl Animal for Student {
    fn shout(&self) {
        println!("animal...");
    }
}
impl Student {
    // 如果没有自己实现自己的方法,
    // 上面2个trait 中都有shout, main中调用 d.shout() 会报错
    fn shout(&self) {
        println!("类自己的方法...");
    }
}
fn main() {
    let d = &Student;
    d.shout(); // 类自己的方法...
    // <Type as Trait>:: 完全限定语法,
        // 在没有参数的情况下使用Person::shout()会报错, 需要用这个语法才可以
    //  使用  Student 实现Person 这个trait 时的shout 方法
    <Student as Person>::shout(d); // person...
    Person::shout(d); // person...
    <Student as Animal>::shout(d); // animal...
    Animal::shout(d); // animal...
}

6.6 标记用 trait

Important
  • 这些trait 没有任何行为,里面是空的. 是给编译器用的, 会给类型打上一个标记,这些标记会影响编译器的静态检查和代码生成
  • 分别是 Copy ,Send, Sync, Sized 这4个
  • Sized trait
    • 表示类型能确定大小
    • 编译阶段能确定大小的类型,编译器就自动为其实现该trait

我们分别在其他地方详细说明

7 返回值指定trait

fn returns_summarizable() -> impl Summary {
    Tweet {
        username: String::from("horse_ebooks"),
        content: String::from(
            "of course, as you probably already know, people",
        ),
        reply: false,
        retweet: false,
    }
}

下面这个会报错

fn returns_summarizable(switch: bool) -> impl Summary {
    if switch {
        NewsArticle {
            headline: String::from(
                "Penguins win the Stanley Cup Championship!",
            ),
            location: String::from("Pittsburgh, PA, USA"),
            author: String::from("Iceburgh"),
            content: String::from(
                "The Pittsburgh Penguins once again are the best \
                 hockey team in the NHL.",
            ),
        }
    } else {
        Tweet {
            username: String::from("horse_ebooks"),
            content: String::from(
                "of course, as you probably already know, people",
            ),
            reply: false,
            retweet: false,
        }
    }
}

8 运算符重载

use std::ops::Add;

#[derive(Debug, Copy, Clone, PartialEq)]
struct Point {
    x: i32,
    y: i32,
}
// 给Point 实现Add trait, Add trait 定义了+加法 会如何操作
impl Add for Point {
    type Output = Point;

    fn add(self, other: Point) -> Point {
        Point {
            x: self.x + other.x,
            y: self.y + other.y,
        }
    }
}

fn main() {
    assert_eq!(
        Point { x: 1, y: 0 } + Point { x: 2, y: 3 },
        Point { x: 3, y: 3 }
    );
}

我们看下Add trait的定义,发现实际带有类型参数,默认值是Self

pub trait Add<Rhs = Self> {
    // ...
}

不同类型的+ 加法重载

use std::ops::Add;

struct Millimeters(u32);
struct Meters(u32);

impl Add<Meters> for Millimeters {
    type Output = Millimeters;

    fn add(self, other: Meters) -> Millimeters {
        Millimeters(self.0 + (other.0 * 1000))
    }
}

fn main() {
    let mm = Millimeters(10);
    let m = Meters(1);

    let sum = mm + m;
    // sum is of type Millimeters
    println!("sum is {}", sum.0); // 1010
    // 注意这样是不行的, 顺序是有关系的
    // let sum2 = m + mm;
}

9 trait object

9.1 使用

我们有这样一个需求, 创建一个GUI 工具, 会遍历一个列表, 调用每个元素的draw方法

我们很快就能想到使用泛型,试试看

pub trait Draw {
    fn draw(&self);
}
pub struct ScreenWithGeneric<T: Draw> {
    pub components: Vec<T>,
}

impl<T> ScreenWithGeneric<T>
where
    T: Draw,
{
    pub fn run(&self) {
        for component in self.components.iter() {
            component.draw();
        }
    }
}

pub struct Button {}

impl Draw for Button {
    fn draw(&self) {
        println!("Button");
    }
}
struct SelectBox {}
impl Draw for SelectBox {
    fn draw(&self) {
        println!("SelectBox");
    }
}

fn main() {
    let botton = Button {};
    let botton2 = Button {};
    let select_box = SelectBox {};
    let s = ScreenWithGeneric {
        // 报错了, 类型不一致了
        components: vec![botton, select_box],
    };
    s.run();
}

结果发现行不通, 问题的关键是我们只要列表里的元素能够执行 draw 方法就行, 不管它是什么类型. 有什么办法呢? trait object 可以解决这个问题


pub trait Draw {
    fn draw(&self);
}
pub struct Screen {
    // Box<dyn Draw> : 定义了一个 trait object
    // 表示 Box里的元素必须都实现了 Draw 这个trait 就行, 不管它是什么类型
1    pub components: Vec<Box<dyn Draw>>,
}
impl Screen {
    pub fn run(&self) {
        for component in self.components.iter() {
            component.draw();
        }
    }
}
pub struct Button {}

impl Draw for Button {
    fn draw(&self) {
        println!("Button");
    }
}
struct SelectBox {}
impl Draw for SelectBox {
    fn draw(&self) {
        println!("SelectBox");
    }
}
fn main() {
    let button = Button {};

    let select_box = SelectBox {};
    let s = Screen {
        // 这里ok了
        components: vec![Box::new(button), Box::new(select_box)],
    };
    s.run();
}
1
使用Box 包起来很好理解, 因为实现了Draw 这个trait的类型的大小不知道,直接用指针指向,所以用Box装箱

9.2 动态派发

Tip
  1. 当你使用泛型函数或方法时,编译器会为每个具体使用的数据类型生成专门的代码。这种机制称为单态化(Monomorphization),单态化后的代码 会执行静态派发
// 使用trait约束泛型T,这里T必须实现了Summary trait
fn notify<T: Summary>(item: T) {
    println!("Breaking news! {}", item.summarize());
}

// 编译器会为每个具体使用的数据类型生成专门的代码,这样,当调用notify(tweet)和notify(article)时,直接调用的就是针对Tweet和NewsArticle预先生成好的、类型特定的函数,这就是静态派发的过程

// 为Tweet类型特化生成的notify函数
fn notify_Tweet(item: Tweet) {
    println!("Breaking news! {}", item.summarize());
}

// 为NewsArticle类型特化生成的notify函数
fn notify_NewsArticle(item: NewsArticle) {
    println!("Breaking news! {}", item.summarize());
}

动态派发 ,使用dyn trait会用动态派发, 无法在编译期确定你调用的是哪个方法,会在运行时确定

9.3 对象安全

只能把满足对象安全的trait 转为 trait object

  • 安全的标准
    • 方法返回类型不是Self
    • 方法中没有泛型类型参数

10 Sized Trait

动态大小的类型 DST (Dynamically Sized Types)

Note
  • 类型可以说是规定了你如何去使用一块内存: 占用多少空间, 怎么读写,比如给你弄了写方法, 方便读写
  • 在数组里, [1i32,2] 占用了8字节 [1i32,2,3] 12个字节, 这2个是不同的类型, 虽然我们都说他们是数组
let s1: str = "Hello there!";
let s2: str = "How's it going?";

// 这个就ok了, 因为这个 &str 类型的 占用空间是确定的,
// 可以说动态类型的数据的一种通用使用方式, 就是一个指针去指向它的实际数据,其他比如长度等信息
let s3: &str = "Hello there!";
上面报错了, 我们分析一下
  1. 前面2行报错了, 你可能会疑惑? s1和s2的占用多少空间是很确定的样子,好像没问题?
  2. s1 s2 两个变量是同一个str类型,但是看起来他们占用的空间却不一样, 这就有问题了
  3. rust 需要在栈上为变量分配固定大小的内存空间, 这需要在编译期就知道类型的大小, 而该str类型大小不确定

为了处理这种动态大小的类型,rust提供了Sized trait 来去确定一个类型的大小在编译器是否是已知的.

在编译期知道大小的类型, rust会为其自动的实现一个trait (Sized)

fn generic<T>(t: T) {
    // --snip--
}

上面的代码实际上 等同与, rust 会为 每个泛型函数隐式添加 Sized trait约束

fn generic<T: Sized>(t: T) {
    // --snip--
}

默认情况下, 泛型函数只能在编译期知道其类型大小的情况下工作。但是, 你可以使用以下特殊语法来放宽这一限制:

// ?Sized 表示 T的大小可能是不确定的
fn generic<T: ?Sized>(t: &T) {
    // --snip--
}

11 一些trait

11.1 类型转换相关

From trait 让你可以从另外一个B类型的数据,创建一个A类型的数据

let my_str = "hello";
// 我们常用到的 String::from  就是String 类型 实现了 From trait
let my_string = String::from(my_str);
自定义实现From
use std::convert::From;

#[derive(Debug)]
struct Number {
    value: i32,
}

impl From<i32> for Number {
    fn from(item: i32) -> Self {
        Number { value: item  }
    }
}

fn main() {
    let num = Number::from(30);
    println!("My number is {:?}", num);

    // 如果你实现了from , 那么对方也自动实现了into
    let int = 5;
    // 试试删除类型说明
    let num: Number = int.into();
    println!("My number is {:?}", num);
}
自定义实现into
use std::convert::Into;

#[derive(Debug)]
struct Number {
    value: i32,
}

impl Into<Number> for i32 {
    fn into(self) -> Number {
        Number { value: self }
    }
}

fn main() {
    let int = 5;
    // Try removing the type annotation
    let num: Number = int.into();
    println!("My number is {:?}", num);
}
Caution
  • 用于易出错的转换, 所以它的返回值是 Result 型
use std::convert::TryFrom;
use std::convert::TryInto;

#[derive(Debug, PartialEq)]
struct EvenNumber(i32);

impl TryFrom<i32> for EvenNumber {
    type Error = ();

    fn try_from(value: i32) -> Result<Self, Self::Error> {
        if value % 2 == 0 {
            Ok(EvenNumber(value))
        } else {
            Err(())
        }
    }
}

fn main() {
    // TryFrom

    assert_eq!(EvenNumber::try_from(8), Ok(EvenNumber(8)));
    assert_eq!(EvenNumber::try_from(5), Err(()));

    // TryInto

    let result: Result<EvenNumber, ()> = 8i32.try_into();
    assert_eq!(result, Ok(EvenNumber(8)));
    let result: Result<EvenNumber, ()> = 5i32.try_into();
    assert_eq!(result, Err(()));
}
Caution
  • 要把任何类型转换成 String, 只需要实现那个类型的 ToString trait. 然而不要直接这么做, 您应该实现fmt::Display trait,它会自动提供 ToString
use std::fmt;

struct Circle {
    radius: i32
}

impl fmt::Display for Circle {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Circle of radius {}", self.radius)
    }
}

fn main() {
    let circle = Circle { radius: 6 };
    println!("{}", circle.to_string());
}
Tip

只要对目标类型实现了 FromStr trait, 就可以用 parse 把字符串转换成目标类型

FromStr定义一个类型如何从字符串转换而来
use std::str::FromStr;

// 定义一个简单的结构体,它包含一个 u32 类型的值
struct MyNumber {
    value: u32,
}

// 为 MyNumber 实现 FromStr trait
impl FromStr for MyNumber {
    type Err = &'static str; // 定义错误类型为 &'static str

    // from_str 方法尝试将字符串解析为 MyNumber
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        // 尝试将字符串转换为 u32
        let value = s.parse::<u32>().map_err(|_| "Parse error")?;
        // 如果成功,创建并返回 MyNumber 实例
        Ok(Self { value })
    }
}

fn main() {
    // 使用 parse::<MyNumber>() 方法将字符串转换为 MyNumber
    // 如果不实现 MyNumber 类型的from_str 方法, 是不能 parse的
    let number = "42".parse::<MyNumber>().unwrap();
    println!("Parsed value: {}", number.value);

    // 如果解析失败,会返回一个错误
    if let Err(e) = "not a number".parse::<MyNumber>() {
        println!("Failed to parse: {}", e);
    }

    let parsed: i32 = "5".parse().unwrap();
    let turbo_parsed = "10".parse::<i32>().unwrap();
}

11.2 打印相关

11.2.1 Display

Note
  • 实现了该trait, 类型就有了to_string方法
  • 只有实现了该trait , 才能使用 println!("{}",x) 打印
use std::fmt;

struct Circle {
    radius: i32
}
// 实现这个trait ,就有了 to_string 方法
impl fmt::Display for Circle {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Circle of radius {}", self.radius)
    }
}

fn main() {
    let circle = Circle { radius: 6 };
    let c=circle.to_string();
    println!("{}", c);
}

11.3 index 索引操作 trait

use std::ops::{Index, IndexMut};

// 定义一个简单的 Collection 类型
struct Collection<T> {
    items: Vec<T>,
}

// 为 Collection 实现 Index trait(只读操作)
impl<T> Index<usize> for Collection<T> {
    type Output = T;

    fn index(&self, index: usize) -> &Self::Output {
        &self.items[index]
    }
}

// 为 Collection 实现 IndexMut trait(可变操作)
impl<T> IndexMut<usize> for Collection<T> {
    fn index_mut(&mut self, index: usize) -> &mut Self::Output {
        &mut self.items[index]
    }
}

fn main() {
    // 创建一个可变的 Collection 实例
    let mut collection = Collection {
        items: vec![10, 20, 30, 40, 50],
    };

    // 使用索引操作读取元素
    println!("原始第一个元素: {}", collection[0]);
    println!("原始第三个元素: {}", collection[2]);

    // 使用索引操作修改元素
    collection[0] = 100;
    collection[2] = 300;

    // 再次读取修改后的元素
    println!("修改后第一个元素: {}", collection[0]);
    println!("修改后第三个元素: {}", collection[2]);
}

12 derive 自动派生

Tip
  • 设计缘由
    • rust 为某些类型实现某些trait时, 代码基本是固定的,重复的,是一件麻烦的事情, 不难想到应该实现自动化处理.
  • 使用方法
    • 因此rust设计一个属性, 就是在你想要实现名为XXX的trait的类型前面写#[derive(XXX)], 它可以自动为你的结构体或枚举实现某些 trait, 也就是rust 编译器会自动为你生成这些 traits 的实现代码
  • 以下是一些常用的可以通过 derive 自动实现的 trait:
    • Debug: 用于格式化输出,使得结构体或枚举在调试时可以打印其字段
    • Clone: 允许创建一个值的副本
    • Copy: 使得类型可以按位复制,而不是移动
    • PartialEq 和 Eq: 用于比较两个值是否相等
    • PartialOrd 和 Ord: 用于比较两个值的顺序
    • Default: 提供一个类型的默认值
    • Hash: 允许类型被用作哈希表中的键

12.1 Debug

派生Debug trait后, 才能使用println!(“{:?}”)中{:?}{:#?}这种打印方式

#[derive(Debug)]
struct Cat(i32);
// 添加这个就自动实现了Debug trait
/*
编译器会自动为你 加上
impl Debug for Dog {
  // ...
}
*/
#[derive(Debug)] 
struct Dog(i32);
fn main() {
    println!("Cat: {:?} ", Cat(5)); // 报错了
    println!("Dog: {:?} ", Dog(5)); // ok
}

12.2 Default

Caution
  • 一般我们也不用, 首先它相当于一个构造函数, 但是我们都会自己定义一个 名字可能是new 的 静态方法
// 添加Default 派生宏
#[derive(Debug, Default)]
struct Rectangle {
    width: u32,
    height: u32,
}
fn main() {
    // 可以使用这种方式来创建一个实例
    // 使用方式1
    let rect1: Rectangle = Default::default();
    println!("{:?}", rect1); //
    // 使用方式2
    let rect2 = Rectangle::default();
    println!("{:?}", rect2);
}
Back to top