【Rust笔记】意译解构 Object Safety for trait
意译解构Object Safety for trait
借助【虚表vtable
】对被调用成员函数【运行时·内存寻址】的作法允许系统编程语言Rust
模仿出OOP
高级计算机语言才具备的【专用·多态Ad-hoc Polymorphism
】特性。
计算机高级语言中的“多态”术语是一个泛指。它通常可被细化为
基于继承关系的“子类·多态”
Subtype Polymorphism
— 形状相似而类名不同即是不同。重“名分”轻“事实”。代表语言JAVA
基于接口抽象的“专用·多态”
Ad-hoc Polymorphism
— 突出不同类型间的共性,淡化类型差异。基于“鸭子类型”的“行·多态”
Row Polymorphism
— 类名不同却形状相似即是相兼容。重“事实”轻“名分”。代表语言JS
因为
Rust
不支持类继承,所以它的多态方式仅收敛于
由【
trait Object
+trait method
动态分派】的“专用·多态”由【Lens设计模式 + 过程宏】的“行·多态”
仅抛砖引玉,就不再展开了。
就Rust
生产实践而言,这也是“以时间换空间”缩小编译输出二进制文件体积的绝佳手段。其对WEB
汇编技术方向更是意义深远,因为只要性能够用,WASM
体积越小越好。对webapp
来讲,用过剩的性能换取发布包更小的体积还是很划算的。但,rustc
要求凡是参与【专用多态】抽象的trait
都必须Object Safety
。“对象安全”的中文直译非常令人费解。
但结合【专用多态】技术语境,Object Safety
可“啰嗦地”意译表达为:“trait method
调用端不需要对trait
实现类及其实例对象有任何了解与假设,而仅凭trait
描述自身,就能顺利地寻址和执行trait method
,以及获得trait method
执行反馈”。因此,Safety
不是直译的“安全”,而是意译的“不知”。
@Rustacean 也可将Object Safety
精炼地领会为“对象不知”或倒装一下“不知(类型与)对象(就能执行它的成员方法)”。
trait
对象安全的核心原则
【专用多态】抽象要求trait
将其具体实现类以【动态大小类型DST
】的?Sized
形式呈现给trait method
调用端。即,胖指针(= 数据指针 + 虚表指针)
在编译时,不锁定数据类型。但因指针大小是固定的,所以编译操作依旧能够成功完成。
在运行时,实时度量变量大小,不论它是【堆】变量
Box<dyn Trait>
,还是【栈】变量&dyn Trait
。
以代码语言概括之,trait
和(动态分派)trait method
都必须满足DST
的where Self: ?Sized
限定条件。事实上,where Self: ?Sized
也是rustc
对trait
自身与trait
关联函数的默认限定。
名词解释:
DST
缩写词的全称Dynamic Sized Type
。其含义是“运行时确定大小的数据类型”。所以,它的trait
限定条件是?Sized
。
FST
缩写词的全称Fixed Sized Type
。其含义是“编译时确定大小的数据类型”。所以,它的trait
限定条件是Sized
。
对照【泛型类型参数】记忆
对照点一:
泛型类型参数默认是
FST
,但可where T: ?Sized
选择退出默认约定trait
与trait method
缺省都是DST
,但同时也支持where Self: Sized
选择退出初始限定
对照点二:例程1
泛型类型参数的
Sized
限定条件是可以被书面重申的,虽然这完全没有必要。trait
与trait method
定义却不能书面地限定where Self: ?Sized
。这会导致编译失败,因为?Sized
仅能书面地限定泛型类型参数(的形参)。
判断trait
是否对象安全的极简checklist
旧版The Rust Programming Language
教程曾经列举过操作性极强的筛选标准:
trait method
返回值类型不是Self
trait method
不是【泛型函数】
虽至今其仍在互联网上广为流传,但它对知识内核的过度简化极易误导 @Rustacean 认为Object Safe trait
的全部trait method
都必须是【动态分派】的。其实不然,对象安全trait
也被允许包含编译时【静态分派】的成员方法。事实上,只要trait
自身满足Object Safety
基本规则,它的成员方法
既可以被收录入
vtable
和参与【动态分派】 — 对trait method
隐式类型参数Self
不做任何限定也能编译时被单态化和参与【静态分派】 — 以
where Self: Sized
限定trait method
隐式类型参数Self
同一个trait
定义动/静两用,没毛病!例程2 走出这个知识点误区有助于避免在业务功能开发过程中频繁地“钻牛角尖”和减轻心智痛苦。
trait
自身对象安全的基本原则
trait
定义的隐式类型参数Self
必须是?Sized
的。这也意味着:若有
supertrait
,那么supertrait
也必须是?Sized
的,因为trait Trait: Supertrait {}
就是trait Trait where Self: Supertrait {}
的语法糖。例程3// 因为`supertrait`不是`?Sized`,所以该`trait`不是`Object Safety`的。 trait Trait: Sized {} // 等效写法 - trait Trait where Self: Supertrait {} struct S; impl Trait for S {} let obj: Box<dyn Trait> = Box::new(S); // 不可动态分派。
若
supertrait
是泛型trait
,那么supertrait
泛型类型参数的实参一定不能是Self
,因为Self
编译时类型不确定和不能作为单态化参数。例程4trait Super<A> {} // 该`trait`不是`Object Safety`的,因为它的隐式类型参数`Self`是`Sized`的。 // - 若抹掉`trait`的`where`从句,那么泛型的【静态分派】会抱怨:“编译时,Self的 // 类型大小未知”。总之,左右为难。 trait Trait: Super<Self> where Self: Sized {} struct S; impl<A> Super<A> for S {} impl Trait for S {} let obj: Box<dyn Trait> = Box::new(S); // 失败,因为`Self: Sized`
trait
定义不能包含【关联常量】。例程5// 该`trait`不是`Object Safety`的, trait NotObjectSafe {// 因为它包含了【关联常量】const CONST: i32 = 1; } struct S; impl NotObjectSafe for S {} let obj: Box<dyn NotObjectSafe> = Box::new(S);
trait
定义中非成员方法【关联函数】的隐式类型参数Self
必须被显式地限定为Sized
例程6。即,where Self: Sized
。// `trait`不是`Object Safety`,因为 trait NotObjectSafe {// 它的非成员方法关联函数的隐式类型参数`Self`不是`Sized`,// 而是缺省的`?Sized`fn foo() {} } struct S; impl NotObjectSafe for S {} let obj: Box<dyn NotObjectSafe> = Box::new(S); // 编译失败
因为隐式类型参数
Self
的缺省限定条件就是?Sized
,所以 @Rustacean 需要利用where
从句书面地退出初始限定和重置Self
为Sized
的。// `trait`是`Object Safety`,因为 trait NotObjectSafe {// 它的非成员方法关联函数的隐式类型参数`Self`被显式地限定为`Sized`,fn foo() where Self: Sized {} } struct S; impl NotObjectSafe for S {} let obj: Box<dyn NotObjectSafe> = Box::new(S); // 编译成功
至此,若不考虑trait method
,获得一个【对象安全】的trait
并不难。
对象安全trait
的成员方法
【重申强调】即便trait
定义的全部成员方法都不参与【动态分派】(即,与它配对的虚表是空),但只要满足上节罗列的三项条件,该trait
依旧是“对象安全”的。只不过,它的trait Object
没啥实用意义。
静态分派trait method
因为trait
【关联函数】的缺省抽象形式是【动态分派】,所以 @Rustacean 需要显式地将trait method
隐式类型参数Self
限定为Sized
。即,给trait method
声明添加where Self: Sized
限定条件和退出DST
内存布局模式 例程7。然后,你就再也不用担心这些trait method
是否是【泛型函数】
非
self
形参与返回值类型是否是Self
self
参数数据类型
虽然省心了,但胖指针(堆Box<dyn Trait>
或栈&dyn Trait
)也再点不出这些trait method
了。请仔细阅读下面例程代码中的注释和体会其中的差别。
// 虽然`trait`是`Object Safety`,
trait Trait {// (1) 但它的`trait method`都是静态分派的,和不能从`Box<dyn Trait>`上被调用// — `trait method`的隐式类型参数`Self`都被显示地限定为`Sized`的,// (2) 于是,成员方法的fn returns(&self) -> Self // a. 返回值类型被允许是`Self`where Self: Sized;fn param(&self, other: Self) // b. 非`self`形参也被允许是`Self`数据类型where Self: Sized {}fn typed<T>(&self, x: T) // c. 接受【泛型函数】成员方法where Self: Sized {}// (3) 非成员方法的关联函数必须是静态分派的fn foo()where Self: Sized {} // 手工限定其是静态分派函数
}
struct S;
impl Trait for S {fn returns(&self) -> Self where Self: Sized { S {field: 10} }
}
// 虽然`trait`是`Object Safety`,但
let obj: Box<dyn Trait> = Box::new(S {field: 12});
// (1) 它没有可运行时寻址调用的成员方法。
// obj.returns(); // 失败,因为 where Self: Sized
// (2) 它的`trait method`都必须从实现类的实例对象上被调用
<S as Trait>::foo();
let obj = S {field: 13};
obj.returns();
obj.typed(1);
对象安全的动态分派trait method
虽然【动态分派】是全部trait method
的“天赋技能”,但 @Rustacean 也有义务从编程环节确保trait method
不依赖于trait
实现类的任何【元信息】。即,trait method
函数体对trait
实现类的类型信息不知。
“不知”即是“安全”。“对象安全”还真不如意译为“对象不知”。这多有趣呀!
在书面代码上,@Rustacean 仅需要做到在trait method
定义中,
不出现【泛型类型参数】 例程8。例外,【泛型生命周期参数】还是被允许的。例程9
非
self
形参与返回值类型不能是Self
。关键字Self
代指trait
实现类,但Object safe trait
需要对实现类不知。self
形参的数据类型必须是如下六种之一 例程10只读引用
&Self / &self
可修改引用
&mut Self / &mut self
智能指针
Box<Self>
引用计数
Rc<Self>
原子引用计数
Arc<Self>
不可
swap
内存Pin<P>
。其中,泛型类型参数P
可以是前五种类型中的任意一种。
千万别限定
trait method
的隐式类型参数Self
为Sized
。
条条框框还是比较多的,可得常记频用,才可应用自如。
对象安全trait
的非成员方法关联函数
这类associated functions
概念对等于Typescript
的静态成员方法。“静态”意味着这类关联函数一定不会参与动态分派,但出于未知原因rustc
依旧偏好将其收录虚表vtable
和造成trait Object
实例化失败。所以,Object safe trait
的重要原则之一,就是:
要么,没有非成员方法关联函数
要么,显式地书面限定每个非成员方法关联函数的隐式类型参数
Self
为Sized
。例程11
否则,编译失败。
// `trait Trait`不是对象安全的,
trait Trait {// 因为它的非成员方法关联函数不可动态分派,但还被收录`vtable`fn foo() {} // 给加添加`where Self: Sized`限定条件,可解编译失败
}
struct S;
impl Trait for S {}
let obj: Box<dyn Trait> = Box::new(S);
结束语
【动态分派】是trait
和trait method
初始开启的天赋技能。除了性能极客,@Rustacean 一般想不起刻意地对定它们做静态化处理。但,由于项目历史包袱,在旧trait
定义内遗留的
泛型函数
Self
滥用非成员方法关联函数
导致其不再“对象安全”。咱们既不必埋怨旧代码作者(哎!谁的认知不是逐步深化的呀),也别慌,更别像我一样傻乎乎地立即重构代码(很伤的)。而仅只需要将仅能静态分派关联函数的隐式类型参数Self
限定为Sized
即可。只要虚表不再收录它们,rustc
就不会抱怨了。于是,“同一个trait
既兼容于新/旧代码,还动/静两用”岂不美哉 例程12!可是不值得炫技,因为大量这类trait
代码是馁馁的后期维护心智灾难 — 只能算是变通的“歪招”。
这次分享的内容就是这些。创作不易,希望路过的神仙哥哥、仙女妹妹们评论、点赞、转发呀!相信我在【WEB
汇编】技术方向Rust
栈至今都是最优选择。
相关文章:

【Rust笔记】意译解构 Object Safety for trait
意译解构Object Safety for trait 借助【虚表vtable】对被调用成员函数【运行时内存寻址】的作法允许系统编程语言Rust模仿出OOP高级计算机语言才具备的【专用多态Ad-hoc Polymorphism】特性。 计算机高级语言中的“多态”术语是一个泛指。它通常可被细化为 基于继承关系的“子…...

Spring Boot单元测试入门指南
Spring Boot单元测试入门指南 JUnit是一个成熟和广泛应用的Java单元测试框架,它提供了丰富的功能和灵活的扩展机制,可以帮助开发人员编写高质量的单元测试。通过JUnit,开发人员可以更加自信地进行重构、维护和改进代码,同时提高代…...

《面试1v1》如何能从Kafka得到准确的信息
🍅 作者简介:王哥,CSDN2022博客总榜Top100🏆、博客专家💪 🍅 技术交流:定期更新Java硬核干货,不定期送书活动 🍅 王哥多年工作总结:Java学习路线总结…...

2023秋招面试题持续更新中。。。
目录 1.八股文渐进式MVVM三次握手,四次挥手viteajax组件化和模块化虚拟dom原理流程浏览器内核浏览器渲染过程回流和重绘nextTick 2.项目相关1.声明式导航和编程式导航重写push和replace方法:性能优化图片懒加载路由懒加载 http请求方式 1.八股文 渐进式…...

Java | 数组排序算法
一、冒泡排序 冒泡排序的基本思想是对比相邻的元素值,如果满足条件就交换元素值,把较小的元素移到数组前面,把较大的元素移到数组后面(也就是交换两个元素的位置),这样较小的元素就像气泡一样从底部升到顶…...
android studio 连接SQLite数据库并实现增删改查功能
功能代码及调试代码 package com.example.bankappdemo;import android.annotation.SuppressLint; import android.content.ContentValues; import android.database.sqlite.SQLiteDatabase; import android.os.Bundle; import android.util.Log; import android.view.View; im…...

跑步适合戴什么样的耳机、最好的跑步耳机推荐
每个人对于运动的方式都不尽相同,但大多数热爱运动的朋友都离不开音乐的陪伴。运动和带有节奏感的音乐能够激发我们更多的热情和动力。特别是在夏日的时候,我非常喜欢跑步。在酷热的天气里,如果没有音乐的伴随,跑步会变得单调乏味…...

物联网的通信协议
物联网的通信协议 目录 物联网的通信协议一、UART串口通信1.1 串口通信1.2 异步收发1.3 波特率1.4 串口通信协议的数据帧1.5 优缺点1.5.1 优点1.5.2 缺点 二、I^2^C2.1 I^2^C2.2 I^2^C2.3 数据有效性2.4 起始条件S和停止条件P2.5 数据格式2.6 协议数据单元PDU2.7 优缺点2.7.1 优…...

【业务功能篇56】SpringBoot 日志SLF4J Logback
3.5.1 日志框架分类与选择 3.5.1.1 日志框架的分类 日志门面 (日志抽象)日志实现JCL(Jakarta Commons Logging) SLF4J(Simple Logging Facade for Java)Jul(Java Util Logging) , Log4j , Log4j2 , Logback 记录型日志框架 Jul (Java Util Logging):JDK中的日志…...

leetcode 53. 最大子数组和
2023.7.28 要求找最大和的 连续子数组, 我的思路是用一个temp记录局部最优值,用ans记录全局最优值。 然后在每次for循环进行一个判断:当前遍历元素temp值 是否大于当前遍历元素的值,如果大于,说明temp值是帮了正忙的&a…...
js 下载url返回的excel数据,并解析为json
XLSX GitHub地址:https://github.com/SheetJS/sheetjs/blob/github/dist/xlsx.full.min.js 需要先引入:XLSX.full.min.js // 下载文件的请求 fetch(downloadFileUrl).then(response > {return rsp.blob() }).then(data > {let reader new FileR…...

图文教程:使用 Photoshop、3ds Max 和 After Effects 创建被风暴摧毁的小屋
推荐: NSDT场景编辑器助你快速搭建可二次开发的3D应用场景 1. 在 Photoshop 中设置图像 步骤 1 打开 Photoshop。 打开 Photoshop 步骤 2 我已经将小屋的图像导入到Photoshop中以演示 影响。如果您愿意,可以使用其他图像。 图片导入 步骤 3 由于小…...

学习Maven Web 应用
Maven Web 应用 本章节我们将学习如何使用版本控制系统 Maven 来管理一个基于 web 的项目,如何创建、构建、部署已经运行一个 web 应用。 创建 Web 应用 我们可以使用 maven-archetype-webapp 插件来创建一个简单的 Java web 应用。 打开命令控制台,…...
page allocation stalls for 问题调研
一.现象分析和内存管理基本概念介绍 最近有一台linux出现卡死的状态,系统不反应,无法ssh登录,只能通过电源关机重启操作恢复,重启后登录系统后台,拉取kernel日志,如下 Jul 12 18:48:06 kernel: [141294.374983] send process: page allocation stalls for 10108ms, orde…...

JUC并发工具类
一、ReentrantLock 特点:独占、可重入、公平/非公平、可中断、支持多个条件变量 1、常用api ReentrantLock实现了Lock接口,Lock类规范定义了如下方法 lock():获取锁,调用该方法的线程会获取锁,当锁获得后࿰…...

【雕爷学编程】MicroPython动手做(10)——零基础学MaixPy之神经网络KPU
早上百度搜“神经网络KPU”,查到与非网的一篇文章《一文读懂APU/BPU/CPU/DPU/EPU/FPU/GPU等处理器》,介绍各种处理器非常详细,关于“KPU”的内容如下: KPU Knowledge Processing Unit。 嘉楠耘智(canaan)号…...

MySQL~SQL语句
一、SQL 1.什么是SQL? Structured Query Language:结构化查询语言 每一种数据库操作的方式存在不一样的地方,称为“方言”。 2.SQL通用语法 SQL 语句可以单行或多行书写,以分号结尾 可使用空格和缩进来增强语句的可读性 MyS…...

从零开始构建基于YOLOv5的目标检测系统
本博文从零开始搭建基于YOLOv5模型的目标检测系统(具体系统参考本博主的其他博客),手把手保姆级完成环境的搭建。 (1)首先Windows R输入cmd命令后打开命令窗口,进入项目目录,本博文以野生动物…...
PDF尺寸修改:等比绽放(标准面单100*150mm)
PDF修改尺寸 需要注意:第一个方法返回的是转换后PDF的base64。第二个方法返回的是文件流,这个方法才是转的核心。 /*** 修改PDF尺寸** param pdfUrl PDF链接* param pdfWidthInMillimeters 指定宽 mm* param pdfHeightInMillimeters 指…...

C++ - list介绍 和 list的模拟实现
list介绍 list 是一个支持在常数范围内,任意位置进行插入删除的序列式容器,且这个容器可以前后双向迭代。我们可以把 list 理解为 双向循环链表的结构。 于其他结构的容器相比,list在 任意位置进行插入和函数的效率要高很多;而li…...
内存分配函数malloc kmalloc vmalloc
内存分配函数malloc kmalloc vmalloc malloc实现步骤: 1)请求大小调整:首先,malloc 需要调整用户请求的大小,以适应内部数据结构(例如,可能需要存储额外的元数据)。通常,这包括对齐调整,确保分配的内存地址满足特定硬件要求(如对齐到8字节或16字节边界)。 2)空闲…...

2025年能源电力系统与流体力学国际会议 (EPSFD 2025)
2025年能源电力系统与流体力学国际会议(EPSFD 2025)将于本年度在美丽的杭州盛大召开。作为全球能源、电力系统以及流体力学领域的顶级盛会,EPSFD 2025旨在为来自世界各地的科学家、工程师和研究人员提供一个展示最新研究成果、分享实践经验及…...
postgresql|数据库|只读用户的创建和删除(备忘)
CREATE USER read_only WITH PASSWORD 密码 -- 连接到xxx数据库 \c xxx -- 授予对xxx数据库的只读权限 GRANT CONNECT ON DATABASE xxx TO read_only; GRANT USAGE ON SCHEMA public TO read_only; GRANT SELECT ON ALL TABLES IN SCHEMA public TO read_only; GRANT EXECUTE O…...
反射获取方法和属性
Java反射获取方法 在Java中,反射(Reflection)是一种强大的机制,允许程序在运行时访问和操作类的内部属性和方法。通过反射,可以动态地创建对象、调用方法、改变属性值,这在很多Java框架中如Spring和Hiberna…...
汇编常见指令
汇编常见指令 一、数据传送指令 指令功能示例说明MOV数据传送MOV EAX, 10将立即数 10 送入 EAXMOV [EBX], EAX将 EAX 值存入 EBX 指向的内存LEA加载有效地址LEA EAX, [EBX4]将 EBX4 的地址存入 EAX(不访问内存)XCHG交换数据XCHG EAX, EBX交换 EAX 和 EB…...

用docker来安装部署freeswitch记录
今天刚才测试一个callcenter的项目,所以尝试安装freeswitch 1、使用轩辕镜像 - 中国开发者首选的专业 Docker 镜像加速服务平台 编辑下面/etc/docker/daemon.json文件为 {"registry-mirrors": ["https://docker.xuanyuan.me"] }同时可以进入轩…...

如何理解 IP 数据报中的 TTL?
目录 前言理解 前言 面试灵魂一问:说说对 IP 数据报中 TTL 的理解?我们都知道,IP 数据报由首部和数据两部分组成,首部又分为两部分:固定部分和可变部分,共占 20 字节,而即将讨论的 TTL 就位于首…...

Mysql中select查询语句的执行过程
目录 1、介绍 1.1、组件介绍 1.2、Sql执行顺序 2、执行流程 2.1. 连接与认证 2.2. 查询缓存 2.3. 语法解析(Parser) 2.4、执行sql 1. 预处理(Preprocessor) 2. 查询优化器(Optimizer) 3. 执行器…...
【学习笔记】erase 删除顺序迭代器后迭代器失效的解决方案
目录 使用 erase 返回值继续迭代使用索引进行遍历 我们知道类似 vector 的顺序迭代器被删除后,迭代器会失效,因为顺序迭代器在内存中是连续存储的,元素删除后,后续元素会前移。 但一些场景中,我们又需要在执行删除操作…...

数学建模-滑翔伞伞翼面积的设计,运动状态计算和优化 !
我们考虑滑翔伞的伞翼面积设计问题以及运动状态描述。滑翔伞的性能主要取决于伞翼面积、气动特性以及飞行员的重量。我们的目标是建立数学模型来描述滑翔伞的运动状态,并优化伞翼面积的设计。 一、问题分析 滑翔伞在飞行过程中受到重力、升力和阻力的作用。升力和阻力与伞翼面…...