trait
trait 与 TS 中的接口(interface)的功能类似,但也不完全相同,比如说 interface 中可以定义属性和方法,而 trait 中只能定义方法。
定义 trait
使用 trait 关键字后边跟 trait 名称,大括号中标识具有该 trait 的类型应该实现的方法:
trait Summary {
// 一个trait可以包含多个方法:每个方法签名占据单独一行并以分号结尾。
fn summarize(&self) -> String;
}
复制代码
为类型实现 trait
可以理解为 TS 中的 implements 关键字,在 rust 中使用 impl 后面跟 trait 名称,再跟 for 关键字跟类型名称,下面为为 NewsArticle 与 Tweet 类型实现 Summary trait:
struct NewsArticles {
headlline: String,
content: String,
author: String,
location: String
}
// 为NewsArticles实现Summary
impl Summary for NewsArticles {
fn summarize(&self) -> String {
// self表示当前调用方法的具体类型,跟TS中的this很像
format!("{}-{}-{}-{}", self.headlline, self.author, self.location, self.content)
}
}
let news = NewsArticles {
headlline: String::from("headline"),
content: String::from("content"),
author: String::from("author"),
location: String::from("location")
};
// 调用summarize()方法
println!("{}", news.summarize()); // headline-author-location-content
struct Tweet {
username: String,
content: String,
reply: String,
retweet: String,
}
// 为Tweet实现Summary
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}-{}-{}-{}", self.username, self.content, self.reply, self.retweet)
}
}
let tweet = Tweet {
username: String::from("username"),
content: String::from("content"),
reply: String::from("reply"),
retweet: String::from("retweet"),
};
// 调用summarize()方法
println!("{}", tweet.summarize()) // username-content-reply-retweet
复制代码
为外部结构实现自定义 trait:
// 为标准库实现自定义trait
impl Summary for String {
fn summarize(&self) -> String {
"impl Summary for String".to_string()
}
}
// 调用为字符串实现的summarize()方法
println!("{}", String::from("abc").summarize()) // impl Summary for String
复制代码
为我们定义的结构实现外部库的 trait:
use std::fmt;
// 实现标准库中的Display trait后可以将类型在控制台打印
// 这里先不用关注代码细节,只需了解可以为我们自定义的类型实现标准库中的trait即可
impl fmt::Display for Tweet {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "({}, {}, {}, {})", self.username, self.content, self.reply, self.retweet)
}
}
// 实现Display后能够正常打印tweet,而无需加{:?}启用Debug模式
println!("{}", tweet); // (username, content, reply, retweet)
复制代码
为外部结构体实现外部的 trait:
// 这里为标准库中的Vec类型实现标准库中的Display trait
impl<T> std::fmt::Display for std::vec::Vec<T> {
// 报错,只有当trait或或者其中一个类型定义于我们的库中时,
// 才能为该类型实现对应的trait。
}
复制代码
上边这种报错是因为 rust 存在孤儿规则:之所以这么命名是因为它的父类型没有定义在当前库中。这一规则也是程序一致性(coherence)的组成部分,它确保了其他人所编写的内容不会破坏到你的代码,反之亦然。如果没有这条规则,那么两个库可以分别对相同的类型实现相同的 trait,rust 将无法确定应该使用哪一个版本。
默认实现
trait 的方法签名可以做默认实现,减少多个类型的方法相同时的重复代码书写:
trait User {
fn user(&self) -> String {
String::from("unknow")
}
}
// 为NewsArticles实现User trait
impl User for NewsArticles {
// 由于user有默认实现,所以这里可以不用实现
}
println!("{}", news.user()) // unknow
复制代码
还可以在默认实现中调用相同 trait 中的其他方法,哪怕这些方法没有默认实现:
trait User {
fn user(&self) -> &String;
fn info(&self) -> &String {
// user()没有默认实现,但是可以调用,因为在结构体实现User trait时
// 一定会实现user()方法
self.user()
}
}
impl User for NewsArticles {
// 这里会实现user()方法
fn user(&self) -> &String {
&self.author
}
}
println!("{}", news.info()) // author
复制代码
使用 trait 来约束参数
使用 impl 关键字,在函数中要求参数必须实现了某个 trait:
fn notify(item: impl Summary) { // 要求item实现了Summary
println!("{}", item.summarize())
}
notify(news) // headline-author-location-content
复制代码
上边其实只是一种语法糖,完整的”trait 约束“是用泛型来实现的:
// 在泛型参数后面跟要求实现的trait
fn notify<T: Summary>(item: T) {
println!("{}", item.summarize())
}
复制代码
impl Trait 语法糖更适用于短小的示例,而 trait 约束则更适用于复杂情形,例如多个参数的时候,trait 约束更加简洁:
// impl Trait
fn notify(item: impl Summary, item2: impl Summary) {}
// trait约束
fn notify<T: Summary>(item: T, item2: T) {}
复制代码
允许多个 trait 对参数做限制:
// 使用加号来要求item需要同时实现Summary和Display两个trait
fn notify<T: Summary + std::fmt::Display>(item: T) {
println!("{}", item.summarize())
}
复制代码
使用 where 语法简化 trait 限制:
fn some_function<T: Summary + std::fmt::Display, U: Debug + Clone>(param1: T, param2: U) {}
// 等同于:
fn some_function<T, U>(param1: T, param2: U) where
T: Summary + std::fmt::Display,
U: Summary + Clone {
// ...
}
复制代码
限制返回值类型
使用 trait 还可以要求返回值实现了某个 trait:
// 要求返回值实现了Summary trait
fn notify(item: String) -> impl Summary {
Tweet {
username: item,
content: String::from("content"),
reply: String::from("reply"),
retweet: String::from("retweet"),
}
}
notify("str".to_string());
复制代码
特殊情况,Tweet 和 NewArticles 都实现了 Summary,却无法通过编译:
fn notify(switch: bool) -> impl Summary {
if switch {
NewsArticles {
headlline: String::from("headline"),
content: String::from("content"),
author: String::from("author"),
location: String::from("location"),
}
} else {
// 报错, if 和 else类型不兼容,这是由于trait的工作方式导致的
// 后便会讲如何使用”trait对象“来存储不同类型的值
Tweet {
username: String::from("username"),
content: String::from("content"),
reply: String::from("reply"),
retweet: String::from("retweet"),
}
}
}
notify(true);
复制代码
使用 trait 解决泛型章节的 largest 函数问题
还记得前面章节中 largest 函数吗,当时忽略了报错的问题,这里我们来使用 trait 来解决,先回忆一下这个函数:
// 求数组中的最大值
fn largest<T>(list: &[T]) -> T {
let mut largest = list[0];
for &item in list.iter() {
if item > largest { // 这里会报错,因为T可能是任意类型,并不是所有类型都可以比较
largest = item
}
}
largest
}
println!("{}", largest(&['a','b','c','d']))
println!("{}", largest(&[1,2,3,3]))
复制代码
使用 trait 约束来解决:
// 要求T类型实现PartialOrd和Copy trait
// PartialOrd用于实现比较功能,
// Copy用于取值时对值进行复制,而不是所有权的转移
fn largest<T: std::cmp::PartialOrd + Copy>(list: &[T]) -> T {
let mut largest = list[0]; // 这里需要Copy,因为比如参数全是String类型时,成员所有权是不能被移出的
for &item in list.iter() {
if item > largest { // 这里需要std::cmp::PartialOrd,因为并不是所有数据都是支持比较的
largest = item
}
}
largest
}
println!("{}", largest(&['a', 'b', 'c', 'd'])); // d
println!("{}", largest(&[1, 2, 3, 3])) // 3
复制代码
使用 trait 来有条件地实现方法
为结构体实现方法时,可以有条件的实现,比如当结构体满足 trait 限制条件时,才为结构体实现某个方法:
#[derive(Debug)]
struct Pair<T> {
x: T,
y: T,
}
// 实现一个含有通用类型T的方法
impl<T> Pair<T> {
fn new(x: T, y: T) -> Self {
Self {
x,y
}
}
}
let pair = Pair::new(100, 50);
println!("{:?}", pair); // Pair { x: 100, y: 50 }
// 实现一个对 结构体成员类型 有要求的方法
// 这里要求T类型实现Display和PartialOrd trait
// 只有实现了这两个trait的类型Pair<T>,
// 才实现cmp_display方法
impl<T: std::fmt::Display + std::cmp::PartialOrd> Pair<T> {
fn cmp_display(&self) -> bool {
if self.x > self.y {
println!("larger:{}", self.x)
} else {
println!("smaller:{}", self.y)
}
}
}
pair.cmp_display(); // larger:100
复制代码
覆盖实现(blanketimplementation)
对满足 trait 约束的所有类型实现,这个特性真的炸裂:
// 定义一个Log trait
trait Log {
fn log(&self);
}
// 注意这里的 for T 直接为所有实现了Display的类型实现log方法
impl<T: std::fmt::Display> Log for T {
fn log(&self) {
println!("{}", self)
}
}
2.log(); // 2
news.log() // error, 上边的news未实现Display
复制代码
不加 trait 限制可以为所有类型实现:
trait LogAlwaysOne {
fn log_one(&self) {
println!("{}", 1)
}
}
// 为所有类型实现LogAlwaysOne trait
impl<T> LogAlwaysOne for T {}
// 例如下面类型都可以调用log_one()方法
1.log_one(); // 1
'1'.log_one(); // 1
"1".log_one(); // 1
news.log_one(); // 1
复制代码
评论