【Rust中级教程】1.15. Trait bounds(Trait 约束)的编译与分派

embedded/2025/2/22 17:36:36/

喜欢的话别忘了点赞、收藏加关注哦(加关注即可阅读全文),对接下来的教程有兴趣的可以关注专栏。谢谢喵!(=・ω・=)
请添加图片描述

1.15.1. 静态分发(static dispatch)

编译泛型代码或者调用dyn Trait(详见【Rust自学】17.2.3. trait对象执行的是动态派发)上的方法时发生了什么?

编译器会针对每个T(每个类型),都将类型或函数复制一部分(每个类型都有自己的函数),这个过程叫单态化(monomorphization)。详见【Rust自学】10.2.6. 泛型代码的性能。

当你构建Vec<i32>HashMap<String, bool>时,编译器会复制它的泛型类型以及所有的实现块。例如Vec<i32>就是把Vec<T>T替换成i32,对Vec做了一个完整的复制,所有遇到的T都换成i32

也就是说编译器会把实例的泛型参数使用具体类型替换。需要注意的是,编译器其实并不会做完整的复制粘贴,他只复制你用的代码。

看个例子:

rust">impl String {pub fn contains(&self, p:impl Pattern) -> bool {p.is_contained_in(self);}
}
  • 这个例子针对String类型实现了一个contains方法
  • contains方法的第二个参数p的trait约束是Patternp没有实际类型,只有trait约束,所以p就相当于泛型参数

p在实际使用时可能会是不同的类型。针对不同的类型,该方法都会复制一遍,因为我们需要知道is_contained_in方法的地址,以便进行调用。CPU需要知道在哪跳转和继续执行。

对于任何给定的p,编译器知道那个地址的类型是实现了Pattern trait方法的。不存在一个可给任意类型的通用地址

PS:我知道你在想Python的动态类型,Python变量本质上是对象的引用,而并非直接存储值

正是因为如此,编译器需要为每个类型复制一个(方法体),每份都有自己的地址来用来跳转。这就是所谓的静态分配(static dispatch),因为对于方法的任何给定副本,我们“分派到”的地址都是静态已知的。

  • 静态(static) 在编程中通常指编译中已知的事物(或可被视为此的)

1.15.2. 单态化(monomorphization)

单态化(monomorphization)指的是从一个泛型类型到多个非泛型类型的过程。Rust的trait就有这个特点。

当编译器优化完代码后,就好像根本没有泛型。每个实例都是单独优化的,具有了所有的已知类型,所以上文例子里的is_contained_in方法调用的执行效率就如同trait不存在一样,没有任何性能损失。

编译器对涉及的类型完全掌握,(在合适的情况下)甚至可以将它们进行inline实现。

  • “对涉及的类型完全掌握”指的是Rust是静态类型的语言,在编译时就能够确定所有变量和函数的类型,不需要在运行时进行类型推导。
  • “将它们进行inline实现”指的是将函数的实现直接展开到调用的地方,避免函数调用的开销。
rust">#[inline(always)]
fn add(a: i32, b: i32) -> i32 {a + b
}fn main() {let x = add(2, 3);  // 可能被编译器优化为 let x = 2 + 3;
}

1.15.3. 单态化的代价

  • 所有实例都需要单独编译,编译时间会因它而增加(如果不能优化编译)
  • 每个单态化的函数会有自己的一段机器码,让程序更大
  • 指令在泛型方法的不同实例间无法共享,CPU的指令缓存效率降低,因为它需要持有相同指令的多个不同副本

1.15.4. 动态分发(dynamic dispatch)

动态分发(dynamic dispatch)使代码可以调用泛型类型上的trait方法,而无需知道具体的类型。

上面的代码例稍作修改即可实现动态分发:

rust">impl String {pub fn contains(&self, p:&impl Pattern) -> bool {p.is_contained_in(&*self);}
}

这个例子中,实现动态分发需要调用者提供两个信息:

  • Pattern的地址
  • is_contained_in的地址

为什么impl Pattern前要加&?

  • 动态分发依赖于trait 对象,而trait对象本质上是一个宽指针(fat pointer,上一篇文章有讲),所以说传入的数据得是一个引用(因为Rust不能确定动态分发类型的内存大小,所以只能用引用)

1.15.5. vtable

实际上,调用者会提供指向一块内存的指针,它叫做虚方法表(virtual method table,简称vtable)

上例中,它持有该类型中所有的trait方法实现的地址,其中一个就是is_contained_in这个方法的地址。

当代码想要调用提供类型的一个trait方法时,就会从vtable查询is_contained_in方法的实现地址并调用。这就允许我们使用相同的函数体,而不关心调用者想要使用的类型。

每个vtable还包含具体类型的布局和对齐信息(总是需要这些信息配合使用)。

1.15.6. 对象安全(Object-Safe)

类型实现了一个trait和它的vtable的组合就形成了一个trait object(trait对象)

大部分trait可转为trait object,但不是所有。例如Clone trait就不行(它的clone方法返回self),Extend trait也不行。这些例子就不是对象安全的(object-safe)

对象安全的具体要求是:

  • trait的所有方法都不能是泛型的,也不可以使用self
  • trait不可以拥有静态方法,因为无法知道在那个实例上调用的方法

1.15.7. self: Sized

self: Sized意味着self无法用于trait object(因为它是!Sized)。

self: Sized用在某个trait,就是要求永远不使用动态分发。

我们也可以将self: Sized用在特定方法上,这时当trait通过trait object访问的时候,该方法就不可用了。

当检查trait对象是否安全的时候,使用了where self: Sized的方法就会免除

1.15.8. 动态分发的优缺点

优点缺点
编译时间减少编译器无法对特定类型优化
提升 CPU 指令缓存效率只能通过 vtable 调用函数
直接调用方法的开销增加
trait object 上的每次方法调用都需要查 vtable

1.15.9. 如何在静态分发和动态分发间选择

静态分发动态分发
在library中使用静态分发在binary中使用动态分发
无法知道用户的需求binary 是最终代码
如果使用动态分发,用户也只能如此动态分发使代码更整洁(省去了泛型参数)
如果使用静态分发,用户可自行选择编译更快
以边际性能为代价

http://www.ppmy.cn/embedded/164395.html

相关文章

Unity摄像机与灯光相关知识

一、Inspector窗口 Inspector窗口可以查看和编辑对象的属性以及设置 其中包含各种组件&#xff0c;例如用Cube对象来举例 1.Sphere(Mesh)组件&#xff1a; 用来决定对象的网格属性&#xff0c;例如球体网格为Sphere、立方体网格为Cube 2.Mesh Renderer组件&#xff1a; 用来设置…

8.python文件

文章目录 1.**文件**1.1**文件是什么**1.2**文件路径**1.3**文件操作**1.3.1**打开文件**1.3.2**关闭文件**1.3.3**写文件**1.3.4**读文件** 1.4**关于中文的处理**1.5**使用上下文管理器** 大家好&#xff0c;我是晓星航。今天为大家带来的是 python文件 相关的讲解&#xff0…

Deepseek 与 ChatGPT:AI 浪潮中的双子星较量

引言 在人工智能飞速发展的当下&#xff0c;AI 语言模型成为了人们关注的焦点。Deepseek 与 ChatGPT 作为其中的佼佼者&#xff0c;各自展现出独特的魅力&#xff0c;引领着 AI 技术的发展潮流。今天&#xff0c;就让我们深入探讨这两款模型&#xff0c;看看它们在 AI 领域中是…

使用 DeepSeek 生成流程图、甘特图与思维导图:结合 Typora 和 XMind 的高效工作流

在现代工作与学习中&#xff0c;可视化工具如流程图、甘特图和思维导图能够极大地提升信息整理与表达的效率。本文将详细介绍如何使用 DeepSeek 生成 Mermaid 文本&#xff0c;结合 Typora 快速生成流程图和甘特图&#xff0c;并通过 Markdown 格式生成思维导图&#xff0c;最终…

题海拾贝:【枚举】P2010 [NOIP 2016 普及组] 回文日期

Hello大家好&#xff01;很高兴我们又见面啦&#xff01;给生活添点passion&#xff0c;开始今天的编程之路&#xff01; 我的博客&#xff1a;<但凡. 我的专栏&#xff1a;《编程之路》、《数据结构与算法之美》、《题海拾贝》 欢迎点赞&#xff0c;关注&#xff01; 1、题…

智信BI:解决Power BI全面兼容问题的新选择

随着企业数据量的持续增长&#xff0c;数据可视化的重要性日益凸显。智信BI作为一套现代化的数据可视化平台&#xff0c;专注于帮助企业应对复杂的数据分析挑战。该平台支持多种报表形式&#xff0c;包括Power BI报表、格式化报表及Office报表&#xff0c;满足不同用户的多样化…

分步教程:使用 i18next 本地化 React 网站

分步教程&#xff1a;使用 i18next 本地化 React 网站 通过本指南学习如何为 React 应用添加多语言支持&#xff0c;实现动态语言切换和翻译管理。 前置条件 基础的 React.js 知识已安装 Node.js 和 npm/yarn通过 create-react-app 创建的 React 项目 步骤 1&#xff1a;安装所…

阿里云通过docker安装skywalking及elasticsearch操作流程

系统 本文使用系统为 Alibaba Cloud Linux 3.2104 LTS 64位 配置为 4核8G PS&#xff1a;最低配置应为2核4G&#xff0c;配置过低无法启动 安装docker 1.卸载旧版本docker yum remove docker \docker-client \docker-client-latest \docker-common \docker-latest \docker-…