trait 告诉编译器某个特定类型拥有可能与其他类型共享的功能。可以通过 trait 以一种抽象的方式定义共享的行为。可以使用 trait bounds 指定泛型是任何拥有特定行为的类型。
trait 类似于其他语言中的 接口,虽然有一些不同。
定义 trait
pub trait Summary {// 一行一个方法签名且都以分号结尾fn summarize(&self) -> String;}
为类型实现 trait
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)}}
使用时,trait 必须和类型一起引入作用域以便使用额外的 trait 方法
use aggregator::{Summary, Tweet};fn main() {let tweet = Tweet {username: String.from("horse_ebooks"),conntent: String.from("of course, as you probably already know, people"),reply: false,retweet: false,};println!("1 new tweet: {}", tweet.summarize());}
不能为外部类型实现外部 trait,例如,不能在 aggregator crate 中为 Vec
默认实现
trait 中某些或全部方法提供默认行为。
pub trait Summary {fn summarize(&self) -> String {String::from("(Read more...)")}}
trait 作为参数
为 NewsArticle 和 Tweet 类型实现了 Summary trait。我们可以定义一个函数 notify 来调用其参数 item 上的 summarize 方法,该参数是实现了 Summary trait 的某种类型。
pub fn notify(item: &impl Summary) {println!("Breaking news! {}", item.summarize());}
我们可以传递任何 NewsArticle 或 Tweet 的实例来调用 notify。
Trait Bound 语法
impl Trait适用于短小的例子,trait bound 则适用于更复杂的场景。
pub fn notify<T: Summary>(item1: &T, item2: &T) {
泛型 T 被指定为 item1 和 item2 的参数限制,如此传递给参数 item1 和 item2 值的具体类型必须一致。
通过 + 指定多个 trait bound
如果 notify 需要显示 item 的格式化形式,同时也要使用 summarize 方法,那么 item 就需要同时实现两个不同的 trait:Display 和 Summary。
pub fn notify(item: &(impl Summary + Display)) {
+语法也适用于泛型的 trait bound:
pub fn notify<T: Summary + Display>(item: &T) {
通过指定这两个 trait bound,notify 的函数体可以调用 summarize 并使用 {} 来格式化 item。
通过 where 简化 trait bound
fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {
上述信息过长
fn some_function<T, U>(t: &T, u: &U) -> i32where T: Display + Clone,U: Clone + Debug{
返回实现了 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,}}
使用 trait bounds 来修复 largest 函数
$ cargo runCompiling chapter10 v0.1.0 (file:///projects/chapter10)error[E0369]: binary operation `>` cannot be applied to type `T`--> src/main.rs:5:17|5 | if item > largest {| ---- ^ ------- T| || T|help: consider restricting type parameter `T`|1 | fn largest<T: std::cmp::PartialOrd>(list: &[T]) -> T {| ++++++++++++++++++++++For more information about this error, try `rustc --explain E0369`.error: could not compile `chapter10` due to previous error
在 largest 函数体中我们想要使用大于运算符(>)比较两个 T 类型的值。这个运算符被定义为标准库中 trait std::cmp::PartialOrd 的一个默认方法。所以需要在 T 的 trait bound 中指定 PartialOrd,这样 largest 函数可以用于任何可以比较大小的类型的 slice。
fn largest<T: PartialOrd>(list: &[T]) -> T {
$ cargo runCompiling chapter10 v0.1.0 (file:///projects/chapter10)error[E0508]: cannot move out of type `[T]`, a non-copy slice--> src/main.rs:2:23|2 | let mut largest = list[0];| ^^^^^^^| || cannot move out of here| move occurs because `list[_]` has type `T`, which does not implement the `Copy` trait| help: consider borrowing here: `&list[0]`error[E0507]: cannot move out of a shared reference--> src/main.rs:4:18|4 | for &item in list {| ----- ^^^^| ||| |data moved here| |move occurs because `item` has type `T`, which does not implement the `Copy` trait| help: consider removing the `&`: `item`Some errors have detailed explanations: E0507, E0508.For more information about an error, try `rustc --explain E0507`.error: could not compile `chapter10` due to 2 previous errors
错误的核心是 cannot move out of type [T], a non-copy slice,对于非泛型版本的 largest 函数,我们只尝试了寻找最大的 i32 和 char。当我们将 largest 函数改成使用泛型后,现在 list 参数的类型就有可能是没有实现 Copy trait 的。这意味着我们可能不能将 list[0] 的值移动到 largest 变量中,这导致了上面的错误。
为了只对实现了 Copy 的类型调用这些代码,可以在 T 的 trait bounds 中增加 Copy!
fn largest<T: PartialOrd + Copy>(list: &[T]) -> T {let mut largest = list[0];for &item in list {if item > largest {largest = item;}}largest}fn main() {let number_list = vec![34, 50, 25, 100, 65];let result = largest(&number_list);println!("The largest number is {}", result);let char_list = vec!['y', 'm', 'a', 'q'];let result = largest(&char_list);println!("The largest char is {}", result);}
使用 trait bound 有条件地实现方法
通过使用带有 trait bound 的泛型参数的 impl块,可以有条件地只为那些实现了特定 trait 的类型实现方法
use std::fmt::Display;struct Pair<T> {x: T,y: T,}impl<T> Pair<T> {fn new(x: T, y: T) -> Self {Self { x, y }}}impl<T: Display + PartialOrd> Pair<T> {fn cmp_display(&self) {if self.x >= self.y {println!("The largest member is x = {}", self.x);} else {println!("The largest member is y = {}", self.y);}}}
类型 Pair<T>总是实现了 new方法并返回一个 Pair<T>的实例,不过在下一个 impl块中,只有那些为 T类型实现了PartialOrdtrait 和Displaytrait 的Pair<T>才会实现cmp_display。对任何满足特定 trait bound 的类型实现 trait 被称为 blanket implementations。
