此部分内容为原文档第十章《泛型、trait 和生命周期》中第二部分关于 trait 的内容,将拆为三部分来学习。
定义泛型的行为也可称为定义可共享的行为,即对于不同类型如果调用相同的方法,这些类型就可以共享相同的行为,这些类型也就可以抽象为泛型。
可以把 trait 简单地概括为可继承及可默认实现的 Java 接口,而且一种类型还可以同时实现多个 trait。
定义 trait
trait 定义是一种将方法签名组合起来的方法,目的是定义一个实现某些目的所必需的行为的集合。
如:
pub trait Display {
fn fmt(&self) -> String;
/*
罗列方法签名
*/
}
为类型实现 trait
使用 impl
声明实现,并使用 for
指定实现的类型。
如:
pub struct Point {
x: i32,
y: i32,
}
impl Display for Point {
fn fmt(&self) -> String {
format!("({}, {})", self.x, self.y)
}
}
在实现时有如下规则:
- 实现完整。
- 相干性(coherence),或者更具体的说是 孤儿规则(orphan rule)。只能为类型实现内部作用域中定义或是导入的 trait。
- 默认实现。当存在默认实现时,已经默认实现的方法可以不必再次实现,也可再次实现来覆盖(重载)原有的默认实现。
trait 的继承
这是第一个与 Java 接口所不同的区别。看一个百度来的例子:
use std::fmt;
fn main() {
struct Point {
x: i32,
y: i32,
}
// OutlinePrint 这个 trait 继承自 fmt::Display,
// 并提供一个额外的 outline_print 功能.
trait OutlinePrint: fmt::Display {
// outline_print 是一个默认实现功能函数.
fn outline_print(&self) {
let output = self.to_string();
println!("{}", output);
}
}
// 这里可以仅声明一下 Point 拥有这个 trait 即可.
// 也可和下面的代码一样, 覆盖默认实现.
impl OutlinePrint for Point {
fn outline_print(&self) {
let output = self.to_string();
let len = output.len();
println!("{}", "*".repeat(len + 4));
println!("*{}*", " ".repeat(len + 2));
println!("* {} *", output);
println!("*{}*", " ".repeat(len + 2));
println!("{}", "*".repeat(len + 4));
}
}
// 这里仍然需要声明 Point 拥有 fmt::Display, 这是 Rust 的语法要求.
impl fmt::Display for Point {
// 为什么这里要写这个方法?
// 那是因为fmt::Display只是定义了一个接口, 并没有实现这个方法.
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
let p = Point {x: 1, y: 2};
p.outline_print();
// output:
/*
**********
* *
* (1, 2) *
* *
**********
*/
}
trait 作为参数
可以想象一下 Java 中使用接口类型作为参数的场景,任何实现了此接口的类的对象均可作为合法参数传入函数。
trait 作为参数是类似的,仅需注意一下使用的语法即可,看例子。
impl Trait
语法
pub struct NewsArticle {
pub headline: String,
pub location: String,
pub author: String,
pub content: String,
}
pub trait Summary {
fn summarize(&self) -> String {
String::from("(Read more...)")
}
}
impl Summary for NewsArticle {}
pub fn notify(item: impl Summary) {
println!("Breaking news! {}", item.summarize());
}
fn main() {
let article = 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."),
};
notify(article);
}
Trait Bound 语法
impl Trait
语法适用于直观的例子,它是 trait bound 语法的一个较长形式的语法糖。trait bound 语法更加适用于复杂场景。
比如,对于下面的函数签名
fn notify(item1: impl Summary, item2: impl Summary)
如果要求传入的 item 具体类型要严格一致,而不能一是 NewsArticle
,另一个是其它实现了 Summary
trait 的类型,这时应该怎么办?
这只有在使用 trait bound 时才有可能。
fn notify<T: Summary>(item1: T, item2: T)
泛型 T
被指定为 item1
和 item2
的参数限制,如此传递给参数 item1
和 item2
值的具体类型必须一致。
通过 +
来指定多个 Trait
可以从两个方面来理解 +
的作用:
+
为类型增加了更为严格的约束,传入的类型参数需要同时实现多个 trait。+
为函数体中增添了更多的已被实现的 trait 提供的方法。
语法如下:
fn notify(item: impl Summary + Display){}
fn notify<T: Summary + Display>(item: T) {}
通过 where
简化 Trait Bound
为了避免过长的 trait bound 语法,Rust 提供了 where 语法糖,看例子:
fn some_function<T: Display + Clone, U: Clone + Debug>(t: T, u: U) -> i32 {}
fn some_function<T, U>(t: T, u: U) -> i32 where T: Display + Clone, U: Clone + Debug {}
返回实现了 trait 的类型
与上面的函数参数类似,在函数签名的返回值位置,也可以使用 impl Trait
语法。
但是需要注意,函数中只能确定的返回一种实现了待返回 trait 的具体类型,而不能出现不同情况返回不同实现了待返回 trait 的具体类型的情况。
使用 trait bound 有条件地实现方法
【待补充】
Trait 是 Rust 另一个核心功能,它是很多其它进阶功能的基础,如迭代和闭包,需要好好掌握。
另外,本部分内容只是介绍了关于 trait 的初级内容,之后官方文档还对 trait 的一些高级内容做了介绍。