赞
踩
特征对象指向实现了 某种特征的类型的实例,这种映射关系是存储在一张表中,可以在运行时通过特征对象找到具体调用的类型方法
可以通过 & 引用或者 Box 智能指针的方式来创建特征对象。
这个说法太抽象了,我们来看一点例子
在Rust特征那一篇文章的最后有一段代码提到
pub trait Summary { fn summarize(&self) -> String; } pub struct Post { pub title: String, // 标题 pub author: String, // 作者 pub content: String, // 内容 } impl Summary for Post { fn summarize(&self) -> String { format!("文章{}, 作者是{}", self.title, self.author) } } pub struct Weibo { pub username: String, pub content: String } impl Summary for Weibo { fn summarize(&self) -> String { format!("{}发表了微博{}", self.username, self.content) } } fn returns_summarizable(switch: bool) -> impl Summary { if switch { Post { title: String::from( "Penguins win the Stanley Cup Championship!", ), author: String::from("Iceburgh"), content: String::from( "The Pittsburgh Penguins once again are the best \ hockey team in the NHL.", ), } } else { Weibo { username: String::from("horse_ebooks"), content: String::from( "of course, as you probably already know, people", ), } } } fn main() { _a =returns_summarizable(true); }
当我们想通过impy trait来企图达到返回多态类型的目的,发现并不允许这样做,会报错。
报错提示我们 if 和 else 返回了不同的类型。要返回相同的类型。
那我们要怎么做?这时候就涉及到特征对象了
我们可以通过&和Box智能指针去包住特征对象
特征对象的语法是:dyn+特征类型名称
dyn 关键字只用在特征对象的类型声明上,在创建时无需使用 dyn
那我们可以看看前面的代码怎么修改
pub trait Summary { fn summarize(&self) -> String; } pub struct Post { pub title: String, // 标题 pub author: String, // 作者 pub content: String, // 内容 } impl Summary for Post { fn summarize(&self) -> String { format!("文章{}, 作者是{}", self.title, self.author) } } pub struct Weibo { pub username: String, pub content: String, } impl Summary for Weibo { fn summarize(&self) -> String { format!("{}发表了微博{}", self.username, self.content) } } fn returns_summarizable(switch: bool) -> Box<dyn Summary> { if switch { Box::new(Post { title: String::from("Penguins win the Stanley Cup Championship!"), author: String::from("Iceburgh"), content: String::from( "The Pittsburgh Penguins once again are the best \ hockey team in the NHL.", ), }) } else { Box::new(Weibo { username: String::from("horse_ebooks"), content: String::from("of course, as you probably already know, people"), }) } } fn main() { let _a = returns_summarizable(true); }
可以看到,返回参数类型说明和返回的对象实例的方式都不同
返回参数类型是 Box<dyn Summar (这里的写法看上面的代码吧,我不知道怎么打转义)
返回的实例对象是使用Box::new的形式构建的
这个涉及到生命周期问题哈哈改不对,后面再改进这方面,如果有人想知道可以评论区艾特我记得更新哈哈
动态分发(dynamic dispatch),直到运行时,才能确定需要调用什么方法。之前代码中的关键字 dyn 正是在强调这一“动态”的特点。
但是在返回参数中,他需要唯一确定返回值的大小,而之前imply trait来作为返回参数,当返回不同类型时,大小就不唯一了,所以就报错了。
那么Box<dyn trait是怎么做的呢
Box<dyn trait这个类型的实例大小是固定的。
ptr是一个引用,这个引用的大小是固定的,指向了底层T实例的数据,T是可变的
vptr 指向一个虚表 vtable,vtable 中保存了类型 Button 或类型 SelectBox 的实例对于可以调用的实现于特征 Draw 的方法。当调用方法时,直接从 vtable 中找到方法并调用。
之所以要使用一个 vtable 来保存各实例的方法,是因为实现了特征 Draw 的类型有多种,这些类型拥有的方法各不相同,当将这些类型的实例都当作特征 Draw 来使用时(此时,它们全都看作是特征 Draw 类型的实例),有必要区分这些实例各自有哪些方法可调用
但是我们用Box<dyn trait传递实例的时候,这个实例仅仅拥有该特征的方法,其它方法(包括原来自己的)无法调用,而实例数据是可以访问的。严格意义上来说,这个Box<dyn trait是特征对象的实例。
不是所有特征都能拥有特征对象,只有对象安全的特征才行。当一个特征的所有方法都有如下属性时,它的对象才是安全的:
在 Rust 中,有两个self,一个指代当前的实例对象,一个指代特征或者方法类型的别名:
trait Draw { fn draw(&self) -> Self; } #[derive(Clone)] struct Button; impl Draw for Button { fn draw(&self) -> Self { return self.clone() } } fn main() { let button = Button; let newb = button.draw(); }
上述代码中,self指代的就是当前的实例对象,也就是 button.draw() 中的 button 实例,Self 则指代的是 Button 类型。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。