2KB项目,专业的源码交易网站 帮助 收藏 每日签到

深入理解 Rust 的动态分派模型

  • 时间:2020-03-30 04:08 编辑:2KB 来源:2KB.COM 阅读:584
  • 扫一扫,手机访问
  • 分享
摘要: 英文原文:Exp
英文原文:Exploring Dynamic Dispatch in Rust 让我做个开场白,我是rust世界的新手(虽然我喜欢它很久了),如果我犯了技术性错误,请让我知道并且我会尽力纠正它们。说到这为止,让我们开始吧!

在下面的代码片段中可以看到我深入研究动态分派的真正动机。假设我想创建一个包含trait对象数组的结构CloningLab(本例中为Mammal):

struct CloningLab {
    subjects: Vec<Box<Mammal>>,
}

trait Mammal {
    fn walk(&self);
    fn run(&self);
}

#[derive(Clone)]
struct Cat {
    meow_factor: u8,
    purr_factor: u8
}

impl Mammal for Cat {
    fn walk(&self) {
        println!("Cat::walk");
    }
    fn run(&self) {
        println!("Cat::run")
    }
}

这段代码工作得很好。你能遍历对象集合并根据需要调用 run 或 walk 函数。然而,当你想再增加一个特性(trait)到特性对象绑定中时,事情开始变得麻烦了:

struct CloningLab {
    subjects: Vec<Box<Mammal + Clone>>,
}

impl CloningLab {
    fn clone_subjects(&self) -> Vec<Box<Mammal + Clone>> {
        self.subjects.clone()
    }
}

错误消息如下:

error[E0225]: only the builtin traits can be used as closure or object bounds
 --> test1.rs:3:32
  |
3 |     subjects: Vec<Box<Mammal + Clone>>,
  |

这种情况出乎我的意料。我一直认为,多重绑定的特性对象(trait object)类似于C++中的多重继承,因此对象应该有多个虚指针,每个都指向对应的继承类,然后再根据匹配情况做分派。考虑到Rust毕竟还是一个非常年轻的语言,语言开发者们可能还不想过早地引入这种复杂性(为了小小的便利而仓促实现的糟糕设计将是沉重的负担),不过我想进一步研究一下这套系统是如何工作的(或者说不能工作的原因)。

Rust中的虚函数表

像C++一样,Rust的动态分派也是通过一个函数指针表实现的(Rust文档中有说明)。根据那个文档,Cat中的Mammal特性对象(trait object)包含两个指针,内存布局如下:

非常奇怪,从中可以看出,对象的数据成员也是通过一个指针来访问的(相当于多出一个间接层),并不像典型的C++内存布局那样。典型的C++内存布局如下:

C++内存布局中,数据成员是紧随vtable指针之后的(没有间接层)。Rust的实现方法比较有意思,当构造特性对象(trait object)时,它产生了一些额外的开销,这不同于C++的实现,C++的实现的好处是对象到基类指针的转换是无开销的(对于多重继承,仅有一点,后面有讲)。不过,Rust实现方式的开销毕竟很小,也确实带来一些好处:如果vtable在多态上下文中实际没有被使用,那它就不必存储它。我想这也可以被看作Rust不鼓励用多态的一个例证,因此这可能是个折衷。

多重绑定的特性对象(trait object)

回到原来的问题,我们来考虑一下如何使用C++来解决。如果我们为一些结构实现了多个特性(就是抽象类),那么结构实例就会有如下的内存布局(以 Mammal和Clone为例):

这里注意我们现在有多个vtable指针。每一个都指向Cat继承的一个对应的基类(Cat包含的那些虚函数)。将一个 Cat* 指针转换为 Mammal* 指针,没有任何开销,但是如果将一个 Cat* 指针转换为 Clone* 指针,编译器将给 this 指针增加一个8字节长度的偏移(这里假设 sizeof(void*) == 8)。(这里解释一下,看上图,Mammal*指针在Cat对象内存中排在第一位,这时Cat*与Mammal*其实是一个东西,内存地址是一样的,所以转换没有开销。后面Cat* 转 Clone*时,Clone* 指针排在 Mammal* 指针后面,所以this指针这时要加上一个偏移量才正确,偏移量就是 Mammal* 指针的大小)

可以想象一下Rust如何做:

因此,现在特性对象包含两个vtable指针了。如果编译器需要实现对 Mammal + Clone 多重特性对象的动态分派,它能够在vtable中选择合适的入口并实现函数调用。可是因为 Rust 现在还不支持结构继承,那么如何决定正确的子对象作为 self 被使用,这个问题就不复存在了。self 将总是指向 data 指针所指向的东西。

这样好像工作的还不错,可是这种方法也存在一些冗余开销。我们持有多个 size/align/drop指针 的拷贝。我们可以通过合并 vtables(虚函数表) 来消除这种冗余。这本质上跟你进行特性继承时编译器所做的一样:

trait CloneMammal: Clone + Mammal{}

impl<T> CloneMammal for T where T: Clone + Mammal{}

这种形式的特性继承(trait inheritance)也是规避特性对象(trait object)限制的一种推荐方法。使用特性继承生成一个没有任何冗余的单个vtable。内存布局如下:

本文中的所有译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接。 2KB翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。


2KB项目(www.2kb.com,源码交易平台),提供担保交易、源码交易、虚拟商品、在家创业、在线创业、任务交易、网站设计、软件设计、网络兼职、站长交易、域名交易、链接买卖、网站交易、广告买卖、站长培训、建站美工等服务

  • 全部评论(0)
上一篇:已是第一篇内容
下一篇:奇虎360 和 go
资讯详情页最新发布上方横幅
最新发布的资讯信息
【计算机/互联网|互联网】深入理解 Rust 的动态分派模型(2020-03-30 04:08)
【计算机/互联网|】mysql 索引过长1071-max key length is 767 byte(2020-03-16 04:01)
【计算机/互联网|】maven build卡死在Downloading的解决方法(2020-03-08 12:13)
【计算机/互联网|】Eclipse里的Java项目按住Ctrl + 左键不能进行跳转问题(2020-03-06 15:32)
【计算机/互联网|互联网】Ceph 性能调优(2020-03-03 23:56)
【计算机/互联网|】百度推送返回状态说明(2020-02-22 18:38)
【计算机/互联网|】tomcat建立示例java web项目(2020-02-15 11:27)
【计算机/互联网|】springboot打包成war,部署到tomcat访问404的问题 (2020-02-13 10:54)
【行业动态|】CES 2020教会了我们关于今年手机的知识:更便宜的可折叠材料,5G及更多(2020-02-10 01:32)
【行业动态|】谷歌Chrome的隐私变化将在今年晚些时候登陆网络(2020-02-10 01:28)
联系我们

Q Q: 7090832

电话:400-0011-990

邮箱:7090832@qq.com

时间:9:00-23:00

联系客服
商家入住 服务咨询 投拆建议 联系客服
0577-67068160
手机版

扫一扫进手机版
返回顶部