pub trait Summary {
fn summarize(&self) -> String {
String::from("(Read more...)")
}
}
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)
}
}
静态分发与动态分发
因为 Bird 是一个 trait,而不是具体类型,它的 size 无法在编译阶段确定,所以编译器是不允许直接使用 trait 作为参数类型和返回类型的。这也是 trait 跟许多语言中的 “interface” 的一个区别。
fn test(arg: Bird) {}
fn test() -> Bird {}
这种时候我们有两种选择:一种是利用泛型:
fn test<T: Bird>(arg: T) {
arg.fly();
}
这样,test 函数的参数既可以是 Duck 类型,也可以是 Swan 类型。实际上,编译器会根据实际调用参数的类型不同,直接生成不同的函数版本,类似 C++ 的 template 一样:
// 伪代码示意
fn test_Duck(arg: Duck) {
arg.fly();
}
fn test_Swan(arg: Swan) {
arg.fly();
}
所以,通过泛型函数实现的“多态”,是在编译阶段就已经确定好了调用哪个版本的函数,因此被称为“静态分派”。
我们还有另外一种办法,实现“多态”,那就是通过指针。虽然 trait 是 DST 类型,但是指向 trait 的指针不是 DST。如果我们把 trait 隐藏到指针的后面,那它就是一个 trait object,而它是可以作为参数和返回类型的。
// 根据不同需求,可以用不同指针类型,如 Box/&/&mut 等
fn test(arg: Box<Bird>) {
arg.fly();
}
这种方式,test 函数的参数既可以是 Box<Duck>
类型,也可以是 Box<Swan>
类型,一样实现了“多态”。但在参数类型这里,已经将“具体类型”信息抹掉了,我们只知道,它可以调用 Bird trait 的方法。而具体调用的是哪个版本的方法,实际上是由这个指针的值来决定的。这就是“动态分派”。
普通写法
pub fn notify(item1: impl Summary, item2: impl Summary) {
使用 trait bounds
,进一步表示不仅需要实现 Summary trait,并且须是同一种类型
pub fn notify<T: Summary>(item1: T, item2: T) {
还可用于声明同时实现多个 trait
pub fn notify(item: impl Summary + Display) {
简化写法
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 也可以作为返回值
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,
}
}
略