写点什么

5 分钟速读之 Rust 权威指南(十八)

用户头像
码生笔谈
关注
发布于: 2021 年 06 月 07 日
5分钟速读之Rust权威指南(十八)

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实现Summaryimpl 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实现Summaryimpl 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:


// 为标准库实现自定义traitimpl 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 traitimpl<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 traitimpl 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 约束“是用泛型来实现的:


// 在泛型参数后面跟要求实现的traitfn notify<T: Summary>(item: T) {  println!("{}", item.summarize())}
复制代码


impl Trait 语法糖更适用于短小的示例,而 trait 约束则更适用于复杂情形,例如多个参数的时候,trait 约束更加简洁:


// impl Traitfn notify(item: impl Summary, item2: impl Summary) {}// trait约束fn notify<T: Summary>(item: T, item2: T) {}
复制代码


允许多个 trait 对参数做限制:


// 使用加号来要求item需要同时实现Summary和Display两个traitfn 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 traitfn 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'])); // dprintln!("{}", 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 traittrait Log {  fn log(&self);}
// 注意这里的 for T 直接为所有实现了Display的类型实现log方法impl<T: std::fmt::Display> Log for T { fn log(&self) { println!("{}", self) }}
2.log(); // 2news.log() // error, 上边的news未实现Display
复制代码


不加 trait 限制可以为所有类型实现:


trait LogAlwaysOne {  fn log_one(&self) {    println!("{}", 1)  }}
// 为所有类型实现LogAlwaysOne traitimpl<T> LogAlwaysOne for T {}
// 例如下面类型都可以调用log_one()方法1.log_one(); // 1'1'.log_one(); // 1"1".log_one(); // 1news.log_one(); // 1
复制代码


发布于: 2021 年 06 月 07 日阅读数: 7
用户头像

码生笔谈

关注

欢迎关注「码生笔谈」公众号 2018.09.09 加入

前端、Rust干货分享,一起成为更好的Javascripter & Rustacean

评论

发布
暂无评论
5分钟速读之Rust权威指南(十八)