陈天 · Rust 编程第一课
陈天
Tubi TV 研发副总裁
23195 人已学习
新⼈⾸单¥68
登录后,你可以任选4讲全文学习
课程目录
已完结/共 65 讲
基础篇 (21讲)
陈天 · Rust 编程第一课
15
15
1.0x
00:00/00:00
登录|注册

24|类型系统:如何在实战中使用trait object?

额外的堆分配开销
执行效率的打折
延迟绑定,得到最大的灵活性
统一处理行为
延迟绑定,决策可以延迟到运行时,得到最大的灵活性
在某个上下文中需要满足某个trait的类型,且类型可能有很多
到了毫秒的量级,性能差别几乎无关紧要
性能差异对于执行效率在数百纳秒以内的函数比较明显
使用&dyn Executor和Box慢很多
使用泛型做静态分发最快
处理KV server的Service结构
闭包
返回值中使用trait object
参数中使用trait object
缺点
好处
用途
性能差异
在数据结构中使用trait object
在函数中使用trait object
trait object
类型系统:如何在实战中使用trait object?

该思维导图由 AI 生成,仅供参考

你好,我是陈天。
今天我们来看看 trait object 是如何在实战中使用的。
照例先来回顾一下 trait object。当我们在运行时想让某个具体类型,只表现出某个 trait 的行为,可以通过将其赋值给一个 dyn T,无论是 &dyn T,还是 Box<dyn T>,还是 Arc<dyn T>,都可以,这里,T 是当前数据类型实现的某个 trait。此时,原有的类型被抹去,Rust 会创建一个 trait object,并为其分配满足该 trait 的 vtable。
你可以再阅读一下第 13 讲的这个图,来回顾 trait object 是怎么回事:
在编译 dyn T 时,Rust 会为使用了 trait object 类型的 trait 实现,生成相应的 vtable,放在可执行文件中(一般在 TEXT 或 RODATA 段):
这样,当 trait object 调用 trait 的方法时,它会先从 vptr 中找到对应的 vtable,进而找到对应的方法来执行。
使用 trait object 的好处是,当在某个上下文中需要满足某个 trait 的类型,且这样的类型可能有很多,当前上下文无法确定会得到哪一个类型时,我们可以用 trait object 来统一处理行为。和泛型参数一样,trait object 也是一种延迟绑定,它让决策可以延迟到运行时,从而得到最大的灵活性。
确认放弃笔记?
放弃后所记笔记将不保留。
新功能上线,你的历史笔记已初始化为私密笔记,是否一键批量公开?
批量公开的笔记不会为你同步至部落
公开
同步至部落
取消
完成
0/2000
荧光笔
直线
曲线
笔记
复制
AI
  • 深入了解
  • 翻译
    • 英语
    • 中文简体
    • 中文繁体
    • 法语
    • 德语
    • 日语
    • 韩语
    • 俄语
    • 西班牙语
    • 阿拉伯语
  • 解释
  • 总结

本文深入介绍了在 Rust 中使用 trait object 进行实战操作的方法和实际应用。通过回顾 trait object 的概念和使用方法,强调了其在灵活性和效率上的折衷。详细讨论了在函数参数和返回值中使用 trait object 的场景,包括使用泛型参数和 trait object 的对比,以及在实际代码中的应用。通过具体的代码示例展示了在参数和返回值中使用 trait object 的实际操作,并介绍了一些实际应用场景。文章还通过性能测试结果展示了使用泛型做静态分发最快,使用 &dyn Executor 和 Box<dyn Executor> 的性能差异,以及对于执行效率低的方法,性能差异的影响。总的来说,大部分情况下,不必太在意 trait object 的性能问题,但在关键路径上需要注意额外的堆内存分配。整体来说,本文通过理论和实践相结合的方式,全面介绍了 trait object 的使用方法和实际应用,对读者快速了解和掌握该技术具有重要参考价值。

仅可试看部分内容,如需阅读全部内容,请付费购买文章所属专栏
《陈天 · Rust 编程第一课》
新⼈⾸单¥68
立即购买
登录 后留言

全部留言(10)

  • 最新
  • 精选
  • Marvichov
    我自己的机器, 用一个unit type来排除struct initialization带来的cost: ``` struct Dummy(); impl Executor for Dummy { fn run(&self) -> Result<Option<i32>, BoxedError> { Ok(Some(0)) } } ``` generics: 243.34 ps trait object: 2.38 ns boxed object: 3.67ns trait object调用时间是generics的9.78倍...居然差这么多!! 不知道为啥box还是比trait object慢; 按理说, unit type是ZST, 没有大小, 应该不涉及heap memory allocation的...速度应该和trait object一样...但实际测出来还是挺大差距的...

    作者回复: 有意思,可能 box 还是没有被完全优化掉?在 ns 的量级,多几条指令可能都会有很大影响。

    2021-10-23
    4
  • 大汉十三将
    唉[苦涩] 看不懂了

    编辑回复: 不着急,理论不懂可以回到实战里再体会体会,实战不稳可以再去复习复习理论,也欢迎在课程的留言区、微信学习群里和大家交流讨论共同进步。加油~

    2023-03-09归属地:云南
    1
  • Bruce
    有见到trait中,除了关联类型type,还有用const定义的占位符,可以讲讲具体的吗

    作者回复: 可以看:https://doc.rust-lang.org/reference/items/associated-items.html#associated-constants。如果你明白了 associate type,那么 associate const 就很好理解。就是类型实现 trait 的时候可以有不同的常量的定义。

    2021-11-04
  • 罗杰
    最近在优化 Go 写的即时对战服务,的确堆内存的分配是消耗性能的一大杀手,泛型的消耗相比堆内存的消耗,应该是可以忽略的。但在高频次的调用上,如果可以优化掉不使用泛型,代码理解与维护上没有问题,也还是尽可能避免使用泛型吧。

    作者回复: 我的建议是库的代码,尤其是处在核心路径上的库,只要泛型不是太过于干扰可读性,能省内存,能节约 CPU cycle 就尽量节约;应用的代码可以写的省心一些。

    2021-10-21
  • D. D
    实现部分需要修改的并不多,把StrategyFn的泛型参数去掉,把reader声明为可变,并在调用函数时传入BufReader的可变引用即可。 我个人觉得修改之后没有带来什么好处,之前的泛型参数并不复杂,而且反而觉得实现时的 Read Write trait bounds让代码读起来很清晰 😂

    作者回复: 嗯,是的。

    2021-10-20
  • A.Y.
    老师好,我这边想咨询一个问题:如果使用trait object将一个闭包放入了map中,然后需要在其他的线程中取出这个闭包执行,该怎么做呢?最近测试了一下,发现编译器提示错误,好像闭包的 trait object并没有实现Sync
    2022-05-07
    1
  • jimmy
    We’ve mentioned that, in Rust, we refrain from calling structs and enums “objects” to distinguish them from other languages’ objects. In a struct or enum, the data in the struct fields and the behavior in impl blocks are separated, whereas in other languages, the data and behavior combined into one concept is often labeled an object. However, trait objects are more like objects in other languages in the sense that they combine data and behavior. But trait objects differ from traditional objects in that we can’t add data to a trait object. Trait objects aren’t as generally useful as objects in other languages: their specific purpose is to allow abstraction across common behavior. --from《The Rust Programming Language》:5. Using Trait Objects That Allow for Values of Different Types
    2024-02-20归属地:广东
  • 阿海
    二刷课程了,朋友们,有没有Rust岗位推荐呢
    2023-05-20归属地:广东
  • 进击的Lancelot
    思考题:实现上修改的地方并不多,只需修改 StrategyFn、match_with 和 default_strategy 的函数签名,将其中的泛型参数去除,然后在调用的地方传入相关的引用即可。比较上,rgrep 的泛型参数还是比较简单直观的,代码也并不会很臃肿,这里用 dynamic trait object 来替换没什么必要。 在泛型参数的版本中,传递进来的 BufReader<R> 不需要是可变的,而改用 trait object 就需要使用 &mut dyn BufRead 而不能是 &dyn BufRead? 我尝试将 mut 关键字去掉,在调用 lines 方法时,产生了 “the `lines` method cannot be invoked on a trait object, this has a `Sized` requirement”。我查阅了 rust 文档,BufRead 中 lines 函数的实现为 fn lines(self) -> Lines<Self> where Self: Sized, { Lines { buf: self } } 因此,这里想向老师请教两个问题: 1. 为什么 &mut dyn BufRead 对象能够调用 lines 方法,而 &dyn BufRead 则不行? 2. 根据第十三讲中所说,只有满足对象安全的前提下才能调用 trait object 的方法,而满足对象安全的情况之一,是不允许携带泛型参数,因为 Rust 里带泛型的类型在编译时会做单态化,而 trait object 是运行时的产物,两者不能兼容。那这里的 lines 方法返回值为 Line<Self>,其中 <Self> 应该算是泛型参数吧,那为什么还能调用呢?
    2022-09-16归属地:广东
  • 老实人Honey
    重新读trait object
    2022-04-09
收起评论
显示
设置
留言
10
收藏
沉浸
阅读
分享
手机端
快捷键
回顶部