【Rust自学】10.3. trait Pt.1:trait的定义、约束与实现

发布于:2025-02-11 ⋅ 阅读:(47) ⋅ 点赞:(0)

喜欢的话别忘了点赞、收藏加关注哦,对接下来的教程有兴趣的可以关注专栏。谢谢喵!(=・ω・=)

题外话:trait的概念非常非常非常重要!!!整个第10章全都是Rust的重难点!!!
请添加图片描述

10.3.1. 什么是trait

trait意为特征、特质。trait用来向Rust编译器描述某种类型具有哪些并且可以与其它类型共享的功能。trait可以以抽象的方式来定义共享的行为。

与trait相关的还有trait bounds(约束)的概念,它可以将泛型类型参数指定为实现了特定行为的类型。换句话说就是要求泛型的类型参数实现了某些triat。

Rust里的trait与其他语言的接口(interface)有点类似,但还是有区别的。

10.3.2. 定义一个trait

类型的行为由该类型本身可调用的方法来组成。有时候在不同的类型上都具有相同的方法,这时候就称这些类型共享了相同的行为。trait提供了一种方式可以把一些方法放到一起,从而定义实现某种目的所必需的一种行为。

  • 定义trait使用关键字trait,在trait的定义内只有方法签名,没有具体实现
  • trait可以有多个方法,每个方法占一行,以;结尾
  • 实现该trait的类型必须提供具体的方法实现,也就是必须有方法体

看个例子:

pub trait Summary {
    fn summarize(&self) -> String;
}

trait前面加上pub代表公共的,这个trait的名字是Summary,里面有一个方法的签名叫summerize,除了&self之外没有其他参数,返回类型是String,然后加一个;就结束了这个签名,它没有方法体,也就是没有具体的实现。当然一个trait下可以有很多个方法签名:

pub trait Summary {
    fn summarize(&self) -> String;
    fn summarize1(&self) -> String;
    fn summarize2(&self) -> String;
    //......
}

10.3.3. 在类型上实现trait

在类型上实现trait与为类型实现方法很类似,但是也有不同之处。

为类型实现方法的写法是impl关键字后面跟着类型就可以了:

impl Yyyy {....}

而在类型上实现trait的写法是:

impl Xxxx for Yyyy {....}
  • Xxxx指的是trait的名
  • Yyyy指的是类型的名
  • 在花括号内需要对trait里的方法签名写下具体的实现

看个例子(lib.rs):

pub trait Summary {
    fn summarize(&self) -> String;
}

pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, self.location)
    }
}

pub struct Tweet {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub retweet: bool,
}

impl Summary for Tweet {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}
  • 结构体NewsArticle表示新闻文章,它有4个字段:headline标题、location地点、author作者、content内容
  • 结构体Tweet表示推特(现在是X了)推文,它有四个字段:username用户名、content内容、reply是否有回复、retweet是否是转发

这两个结构体类型肯定不同,里面的字段大部分也不同。但是它们都可以有一个同样的行为——提取摘要Summary,所以就分别在这两个类型上实现Summary这个trait。

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, self.location)
    }
}

这一段是在NewsArticle实现trait,因为在定义trait时写了summarize的方法签名,所以在这里就得写具体的实现:使用format!这个宏将self.headlineself.authorself.location组成一个字符串返回。

impl Summary for Tweet {
    fn summarize(&self) -> String {
        format!("{}: {}", self.username, self.content)
    }
}

这一段是在Tweet上实现trait,也是一样的写summarize的具体实现:使用format!这个宏将self.usernameself.content组成一个字符串返回。

然后来到main.rs,看它们实例的调用:

use RustStudy::{Summary, Tweet};

fn main() {
    let tweet = Tweet {
        username: String::from("horse_ebooks"),
        content: String::from(
            "of course, as you probably already know, people",
        ),
        reply: false,
        retweet: false,
    };

    println!("1 new tweet: {}", tweet.summarize());
}

记住在main函数里使用之前要先引入作用域,写法是:

use 你的package名::...::你需要的模块

你的package名就是Cargo.toml里的项目名,把它复制下来即可

这里引入Summary是因为使用了在Summary这个trait下的summarize方法;引入Tweet是因为使用了Tweet这个结构体。

看一下输出:

1 new tweet: horse_ebooks: of course, as you probably already know, people

10.3.4. trait的约束

想要在某个类型上实现某个trait的前提条件是:

  • 这个类型(比如说Tweet类型)或这个trait(让Vector实现本地的Summary)是在本地crate里定义的
  • 无法为外部类型实现外部trait。比如说在本地库里标准库Vector实现标准库Display trait。
    这个限制是程序属性的一部分(也就是一致性)。更具体的说是孤儿原则,之所以这么命名是因为它的父类型并没有定义在当前库中。这个规则确保了其他人的代码不能随意破坏你的代码,反之亦然。如果没有这个规则,两个crate可以为同一个类型实现同一个trait,Rust就不知道应该使用哪个实现。

10.3.5. 默认实现

有些时候,为trait中的某些或者是所有方法提供默认行为是非常有用的,它可以使我们无需为每一个类型的实现都提供自定义的行为。我们可以针对某些特定的类型实现trait里的方法。

当我们为某些类型实现trait时,我们可以选择保留或是重载每个方法的默认实现。

之前的写法是:

pub trait Summary {
    fn summarize(&self) -> String;
}

之前的写法只写了方法的签名,没有写实现,而其实可以给它写一个默认的实现

默认实现:

pub trait Summary {
    fn summarize(&self) -> String {
        String::from("(Read more...)")
    }
}

这里的默认实现就是返回一个字符串"(Read more…)"

由于这个方法在trait里面已经有一个默认实现了,所以在具体的类型上就可以直接采用这个默认实现,而不进行自己的实现。

NewsArticle为例,原本它有自己的实现(或者叫做默认实现的重写的实现):

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, self.location)
    }
}

只要删掉这个具体实现就可以让NewsArticle用默认实现:

impl Summary for NewsArticle {}

还有一点需要知道,默认实现的方法可以调用trait中其它的方法,即使这些方法没有默认实现:

pub trait Summary {
	fn summerize_author(&self) -> String
    fn summarize(&self) -> String {
        String::from("(Read more from {}...)", self.summerize_author())
    }
}

summarize的默认实现里调用了summerize_author,即使它只是一个签名,没有具体实现。但如果想要在类型上实现summerize的话就需要先写summerize_author的实现:

impl Summary for NewsArticle {
	fn summerize_author(&self) -> String {
		format!("@{}", self.author)
	}
}

PS:由于NewsArticlesummarize使用的是默认实现,所以就不需要在这里写summerize的默认实现了

这个写法有一点注意:无法从方法的重写实现里面调用默认的实现


网站公告

今日签到

点亮在社区的每一天
去签到