【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返回值类型不是Selftrait 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形参与返回值类型是否是Selfself参数数据类型
虽然省心了,但胖指针(堆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…...
基于Uniapp开发HarmonyOS 5.0旅游应用技术实践
一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架,支持"一次开发,多端部署",可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务,为旅游应用带来…...
Auto-Coder使用GPT-4o完成:在用TabPFN这个模型构建一个预测未来3天涨跌的分类任务
通过akshare库,获取股票数据,并生成TabPFN这个模型 可以识别、处理的格式,写一个完整的预处理示例,并构建一个预测未来 3 天股价涨跌的分类任务 用TabPFN这个模型构建一个预测未来 3 天股价涨跌的分类任务,进行预测并输…...
如何为服务器生成TLS证书
TLS(Transport Layer Security)证书是确保网络通信安全的重要手段,它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书,可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...
3403. 从盒子中找出字典序最大的字符串 I
3403. 从盒子中找出字典序最大的字符串 I 题目链接:3403. 从盒子中找出字典序最大的字符串 I 代码如下: class Solution { public:string answerString(string word, int numFriends) {if (numFriends 1) {return word;}string res;for (int i 0;i &…...
Linux 内存管理实战精讲:核心原理与面试常考点全解析
Linux 内存管理实战精讲:核心原理与面试常考点全解析 Linux 内核内存管理是系统设计中最复杂但也最核心的模块之一。它不仅支撑着虚拟内存机制、物理内存分配、进程隔离与资源复用,还直接决定系统运行的性能与稳定性。无论你是嵌入式开发者、内核调试工…...
0x-3-Oracle 23 ai-sqlcl 25.1 集成安装-配置和优化
是不是受够了安装了oracle database之后sqlplus的简陋,无法删除无法上下翻页的苦恼。 可以安装readline和rlwrap插件的话,配置.bahs_profile后也能解决上下翻页这些,但是很多生产环境无法安装rpm包。 oracle提供了sqlcl免费许可,…...
WEB3全栈开发——面试专业技能点P7前端与链上集成
一、Next.js技术栈 ✅ 概念介绍 Next.js 是一个基于 React 的 服务端渲染(SSR)与静态网站生成(SSG) 框架,由 Vercel 开发。它简化了构建生产级 React 应用的过程,并内置了很多特性: ✅ 文件系…...
Windows电脑能装鸿蒙吗_Windows电脑体验鸿蒙电脑操作系统教程
鸿蒙电脑版操作系统来了,很多小伙伴想体验鸿蒙电脑版操作系统,可惜,鸿蒙系统并不支持你正在使用的传统的电脑来安装。不过可以通过可以使用华为官方提供的虚拟机,来体验大家心心念念的鸿蒙系统啦!注意:虚拟…...
Python学习(8) ----- Python的类与对象
Python 中的类(Class)与对象(Object)是面向对象编程(OOP)的核心。我们可以通过“类是模板,对象是实例”来理解它们的关系。 🧱 一句话理解: 类就像“图纸”,对…...
如何通过git命令查看项目连接的仓库地址?
要通过 Git 命令查看项目连接的仓库地址,您可以使用以下几种方法: 1. 查看所有远程仓库地址 使用 git remote -v 命令,它会显示项目中配置的所有远程仓库及其对应的 URL: git remote -v输出示例: origin https://…...
