改进rust代码的35种具体方法-类型(十九)-避免使用反射

上一篇文章


从其他语言来到Rust的程序员通常习惯于将反思作为工具箱中的工具。他们可能会浪费很多时间试图在Rust中实现基于反射的设计,却发现他们所尝试的事情只能做得不好,如果有的话。这个项目希望通过描述Rust在反思方面做什么和不做什么,以及可以使用什么来节省探索死胡同所浪费的时间。

反思是程序在运行时检查自己的能力。给定一个运行时的项目,它涵盖了以下问题:

  • 关于物品的类型,可以确定哪些信息?
  • 这些信息能做什么?

具有全面反思支持的编程语言对这些问题有广泛的答案。基于反射信息,带有反射的语言通常在运行时支持以下部分或全部内容:

  • 确定变量的类型
  • 探索其内容
  • 修改其字段
  • 调用其方法

具有这种反射支持水平的语言也往往是动态类型语言(例如Python、Ruby),但也有一些值得注意的静态类型语言也支持反射,特别是java和Go。

Rust不支持这种类型的反射,这使得避免反射的建议在这个层面上很容易遵循——这是不可能的。对于来自支持充分反思的语言的程序员来说,这种缺席起初似乎是一个重大差距,但Rust的其他功能提供了解决许多相同问题的替代方法。

C++具有更有限的反射形式,称为运行时类型识别(RTTI)。typeid运算符为每个类型返回一个唯一标识符,用于多态类型的对象(大致:具有虚拟函数的类):

Rust也不支持这种RTTI反思风格,延续了本项目建议易于遵循的主题。

Rust确实支持一些在std::any模块中提供类似功能的功能,但它们是有限的(以我们将探索的方式),因此除非没有其他替代方案,否则最好避免。

std::any的第一个反射状功能起初看起来像魔术——一种确定物品类型名称的方法。以下示例使用用户定义的tname()函数:

let x = 42u32;
let y = vec![3, 4, 2];
println!("x: {} = {}", tname(&x), x);
println!("y: {} = {:?}", tname(&y), y);

显示类型以及值:

x: u32 = 42
y: alloc::vec::Vec<i32> = [3, 4, 2]

该实现由std::any::type_name<T>库函数提供,该函数也是通用的。此函数只能访问编译时信息;没有代码运行来确定运行时的类型。之前中使用的特征对象类型说明了这一点:

let square = Square::new(1, 2, 2);
let draw: &dyn Draw = &square;
let shape: &dyn Shape = &square;

println!("square: {}", tname(&square));
println!("shape: {}", tname(&shape));
println!("draw: {}", tname(&draw));

只有特征对象的类型可用,而不是具体基础项目的类型(Square):

square: reflection::Square
shape: &dyn reflection::Shape
draw: &dyn reflection::Draw

type_name返回的字符串仅适用于诊断——它显然是一个“尽最大努力”助手,其内容可能会发生变化,并且可能不是唯一的——因此不要试图解析type_name结果。如果您需要全局唯一类型标识符,请改用TypeId:

use std::any::TypeId;

fn type_id<T: 'static + ?Sized>(_v: &T) -> TypeId {
    TypeId::of::<T>()
}
println!("x has {:?}", type_id(&x));
println!("y has {:?}", type_id(&y));
x has TypeId { t: 18349839772473174998 }
y has TypeId { t: 2366424454607613595 }

输出对人类的帮助较小,但唯一性的保证意味着结果可以用于代码。然而,通常最好不要直接使用TypeId,而是使用std::any::Any特征,因为标准库具有处理Any实例的附加功能(如下所述)。

Any trait只有一个方法type_id(),它返回实现该trait的类型的TypeId值。你不能自己实现这个特性,因为Any已经为大多数任意类型T提供了一个全面的实现:

impl<T: 'static + ?Sized> Any for T {
    fn type_id(&self) -> TypeId {
        TypeId::of::<T>()
    }
}

总体实现并不涵盖所有类型TT: 'static生命周期绑定意味着,如果T包含任何具有非'static生命周期的引用,则TypeId不会为T实现。这是一个故意施加的限制,因为生命周期不是类型的一部分:TypeId::of::<&'a T>将与TypeId::of::<&'b T>相同,尽管生命周期不同,这增加了混淆和代码不健全的可能性。

第8项中回想一下,特征对象是一个胖指针,它包含指向底层项目的指针,以及指向特征实现vtable的指针。对于Any,vtable有一个条目,用于返回项目类型的type_id()方法,如下图所示:

let x_any: Box<dyn Any> = Box::new(42u64);
let y_any: Box<dyn Any> = Box::new(Square::new(3, 4, 3));

Any特征对象,每个都有指向具体项目和vtable的指针

除了几个间接性外,dyn Any特征对象实际上是原始指针和类型标识符的组合。这意味着标准库可以提供一些额外的通用方法,这些方法是为dyn Any特征对象定义的;这些方法是一些附加类型T的通用的:

  • is::<T>():指示特征对象的类型是否等于某些特定的其他类型T
  • downcast_ref::<T>():返回对具体类型T的引用,前提是特征对象的类型匹配T
  • downcast_mut::<T>():返回对具体类型T的可变引用,前提是特征对象的类型匹配T

观察Any特征只是近似反射功能:程序员选择(在编译时)明确构建一些东西(&dyn Any),以跟踪项目的编译时类型及其位置。只有当构建Any特征对象的开销已经发生时,(例如)向下回归原始类型的能力才有可能。

在相对较少的场景中,Rust具有与项目相关的不同编译时和运行时类型。其中最主要的是特征对象:混凝土类型Square的项目可以强制为该类型实现的特征的特征对象dyn Shape。这种胁迫从一个简单的指针(对象/项目)构建一个胖指针(对象+vtable)。

也从之前文章中回忆起,Rust的特征对象并不是真正面向对象的。aSquare不是Shape;只是Square实现了Shape的接口。特征绑定也是如此:特征绑定Shape: Draw并不意味着is-a;它只是意味着也实现,因为Shape的vtable包括Draw方法的条目。

对于一些简单的特质界限:

trait Draw: Debug {
    fn bounds(&self) -> Bounds;
}

trait Shape: Draw {
    fn render_in(&self, bounds: Bounds);
    fn render(&self) {
        self.render_in(overlap(SCREEN_BOUNDS, self.bounds()));
    }
}

等效特征对象:

let square = Square::new(1, 2, 2);
let draw: &dyn Draw = &square;
let shape: &dyn Shape = &square;

有一个带有箭头的布局(如图3-5所示;从第重复),使问题变得清晰:给定一个dyn Shape对象,没有立即构建dyn Draw特征对象的方法,因为没有办法回到impl Draw for Square的vtable——即使其内容的相关部分(Square::bounds()方法的地址)理论上可恢复的。(这在后续版本的Rust中可能会发生变化;请参阅此项目的最后一节。)

特征边界的特征对象,具有不同的vtable用于DrawShape

与上一张图表相比,很明显,显式构造的&dyn Any特征对象没有帮助。Any允许恢复基础项的原始具体类型,但没有运行时方式来查看它实现的属性,或访问可能允许创建特征对象的相关vtable。

那么,有什么是可用的呢?

要获得的主要工具是特征定义,这符合对其他语言的建议——有效的Java项目65建议“更喜欢界面,而不是反射”。如果代码需要依赖某个项目的某些行为的可用性,请将该行为编码为特征。即使所需的行为不能表示为一组方法签名,也请使用标记特征来指示对所需行为的合规性——这比(例如)自省类名称以检查特定前缀更安全、更有效。

期望特征对象的代码也可以与具有程序链接时不可用的后立代码的对象一起使用,因为它在运行时(通过dlopen(3)或等效的)已动态加载——这意味着通用的单态化是不可能的。

与此相关,反射有时也用于其他语言,以允许同一依赖库的多个不兼容版本同时加载到程序中,绕过只能有一个的链接约束。Rust不需要这个,Cargo已经处理了同一库的多个版本。

最后,宏——特别是derive宏——可用于自动生成在编译时理解项目类型的辅助代码,作为更高效、更类型安全的等同于在运行时解析项目内容的代码。之后文章会讨论了Rust的宏系统。

Rust未来版本中的升级

本项目的文本首次编写于2021年,并一直保持准确,直到该书准备在2024年出版——届时,Rust将添加一项新功能,以改变一些细节。

当U是T的超性状(性状T: U{…})之一时,这个新的“性状上转换”特性可以将一个性状对象dyn T转换为一个性状对象dyn U。在正式发布之前,该功能被锁定在#![feature(trait_upcasting)]上,预计将在Rust 1.76版本发布。

对于前面的示例,这意味着&dyn Shape特征对象现在可以转换为&dyn Draw特征对象,更接近Liskov替换的is-a关系。允许这种转换会对vtable实现的内部细节产生连锁效应,这些细节可能会变得比上图所示的版本更复杂。

然而,此项目的中心点不受影响——Any特征都没有超特征,因此上投的能力不会增加其功能。

06ae6c1d4239b3e971f583be7e1fed1e.png

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/653981.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2024年【西式面点师(中级)】新版试题及西式面点师(中级)考试试卷

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年【西式面点师&#xff08;中级&#xff09;】新版试题及西式面点师&#xff08;中级&#xff09;考试试卷&#xff0c;包含西式面点师&#xff08;中级&#xff09;新版试题答案和解析及西式面点师&#xff08;…

手动安装maven依赖到本地仓库

使用mvn install命令安装jar包到指定的仓库。 命令如下&#xff1a; mvn install:install-file -Dmaven.repo.localC:\Users\liyong.m2\repository -DgroupIdcom.aspose -DartifactIdwords -Dversion18.4 -Dpackagingjar -DfileC:\Users\liyong\Desktop\jar\words-18.4.jar 解释…

JAVA智慧工厂制造生产管理MES系统,全套源码,多端展示(MES与ERP系统的区别和联系)

MES与ERP系统的区别和联系 MES制造执行系统&#xff0c;是一套面向制造公司车间执行层的生产信息化管理系统。MES 可觉得公司提供涉及制造数据管理、计划排产管理、生产调度管理、库存管理、质量管理、人力资源管理、工作中心、设备管理、工具工装管理、采购管理、成本管理、项…

谷歌忙于手动删除自己搜索引擎中奇怪的人工智能答案

该公司确认正在“迅速采取行动”消除人工智能工具的一些奇怪反应。 社交媒体上充斥着谷歌新的人工智能概述产品的例子&#xff0c;这些产品说了一些奇怪的话&#xff0c;从告诉用户在披萨上涂胶水到建议他们吃石头。混乱的推出意味着&#xff0c;随着各种表情包的发布&#xf…

DNS 解析过程

文章目录 简介特点查询方式⚡️1. 浏览器缓存2. 系统缓存&#xff08;hosts文件&#xff09;3. 路由器缓存4. 本地域名服务器5. 根域名服务器6. 顶级域名服务器7. 权限域名服务器8. 本地域名服务器缓存并返回9. 操作系统缓存并返回10. 浏览器缓存并访问流程图 总结 简介 DNS&a…

双向带头链表实现

目录 一. 逻辑结构图解 1. 节点中存储的值 2.逻辑实现 二. 各种功能实现 1. 创建节点函数 2. 初始化哨兵位 3. 尾插 4. 头插 5. 尾删 6. 头删 7. 打印链表值 8. 查找数据&#xff0c;返回节点地址 9. 指定地址后插入节点 10. 删除指定地址节点 11. 销毁链表 三.…

vue实现附件下载 获取到接口的图片,设置宽度100%样式

一、vue实现附件下载&#xff0c;使用a链接 <a class"btn flex_center" target"_blank" :href"localImgSrc(goodsDetail.attachment)" :download"localImgSrc(goodsDetail.attachment)" >立即下载 </a>二、 获取到接口…

七大获取免费https的方式

想要实现https访问最简单有效的的方法就是安装SSL证书。只要证书正常安装上以后&#xff0c;浏览器就不会出现网站不安全提示或者访问被拦截的情况。下面我来教大家怎么去获取免费的SSL证书&#xff0c;又如何安装证书实现https访问。 一、选择免费SSL证书提供商 有多家机构提…

vue3(一):Vue3简介、创建vue3工程、Vue3中的响应式

目录 一.Vue3简介 1.性能提升 2.源码升级 3.拥抱ts 4.新特性 &#xff08;1&#xff09;Composition API&#xff08;组合API&#xff09;&#xff1a; &#xff08;2&#xff09;新的内置组件&#xff1a; &#xff08;3&#xff09;其他改变&#xff1a; 二.创建vue…

【计算机视觉(4)】

基于Python的OpenCV基础入门——色彩空间转换 色彩空间简介HSV色彩空间GRAY色彩空间色彩空间转换 色彩空间转换代码实现: 色彩空间简介 色彩空间是人们为了表示不同频率的光线的色彩而建立的多种色彩模型。常见的色彩空间有RGB、HSV、HIS、YCrCb、YUV、GRAY&#xff0c;其中最…

BLE蓝牙模块在车联网中的智能开锁、数据监控应用

随着科技的不断发展&#xff0c;车联网已经成为了汽车行业的一个热门话题。在这个领域中&#xff0c;BLE蓝牙模块发挥着重要的作用&#xff0c;特别是在智能开锁和数据监控方面的应用。本文将详细介绍BLE蓝牙模块在这两个方面的应用及其优势。   一、智能开锁   1.车辆远程…

从华为云OBS到AWS云上S3:迁移及相关事项

随着云计算的快速发展&#xff0c;企业越来越倾向于将数据存储和管理移到云端。华为云的对象存储服务&#xff08;OBS&#xff09;和亚马逊云服务&#xff08;AWS&#xff09;上的简单存储服务&#xff08;S3&#xff09;是两个备受欢迎的选择。对于那些考虑从华为云OBS迁移到A…

工商银行异地卡兑换泰铢的流程

本文介绍在国内的工商银行&#xff0c;通过现金或银行卡兑换泰国铢等外国货币的纸币或硬币的方法。 最近&#xff0c;准备到泰国旅行&#xff0c;所以需要兑换一些泰铢&#xff0c;防止下飞机到当地后找不到汇率合适、兑换方便的换钱的地方。其中&#xff0c;因为对比发现工商银…

SQL试题使得每个学生 按照姓名的字⺟顺序依次排列 在对应的⼤洲下⾯

学⽣地理信息报告 学校有来⾃亚洲、欧洲和美洲的学⽣。 表countries 数据如下&#xff1a; namecontinentJaneAmericaPascalEuropeXiAsiaJackAmerica 1、编写解决⽅案实现对⼤洲&#xff08;continent&#xff09;列的 透视表 操作&#xff0c;使得每个学生 按照姓名的字⺟顺…

Django5+React18前后端分离开发实战14 React-Router6 入门教程

使用nodejs18 首先&#xff0c;将nodejs切换到18版本&#xff1a; nvm use 18创建项目 npm create vitelatest zdpreact_basic_router_dev -- --template react cd zdpreact_basic_router_dev npm install react-router-dom localforage match-sorter sort-by npm run dev此…

Python-3.12.0文档解读-内置函数ord()详细说明+记忆策略+常用场景+巧妙用法+综合技巧

一个认为一切根源都是“自己不够强”的INTJ 个人主页&#xff1a;用哲学编程-CSDN博客专栏&#xff1a;每日一题——举一反三Python编程学习Python内置函数 Python-3.12.0文档解读 目录 详细说明 概述 语法 参数 返回值 示例 注意事项 应用场景 记忆策略 常用场景…

JD3-40/23漏电继电器 AC220V 50-500mA 0.1s 导轨安装

系列型号&#xff1a; JD3-40/13漏电继电器JD3-40/23漏电继电器JD3-40/33漏电继电器JD3-40/43漏电继电器 JD3-70/13漏电继电器JD3-70/23漏电继电器JD3-70/33漏电继电器JD3-70/43漏电继电器 JD3-100/23漏电继电器JD3-100/43漏电继电器JD3-100/33漏电继电器JD3-100/13漏电继电…

ARM架构与分类

ARM架构&#xff0c;曾称进阶精简指令集机器&#xff08;Advanced RISC Machine&#xff09;更早称作Acorn RISC Machine&#xff0c;是一个32位精简指令集&#xff08;RISC&#xff09;处理器架构。 主要是根据FPGA zynq-7000的芯片编写的知识思维导图总结&#xff0c;没有会…

【NumPy】全面解析NumPy随机数生成器:使用numpy.random的实用技巧

&#x1f9d1; 博主简介&#xff1a;阿里巴巴嵌入式技术专家&#xff0c;深耕嵌入式人工智能领域&#xff0c;具备多年的嵌入式硬件产品研发管理经验。 &#x1f4d2; 博客介绍&#xff1a;分享嵌入式开发领域的相关知识、经验、思考和感悟&#xff0c;欢迎关注。提供嵌入式方向…

【软件设计师】算法

1、算法的效率 时间复杂度:程序从开始到结束所需要的时间 空间复杂度&#xff1a;算法在运行过程中临时占用存储空间大小的度量 时间渐近复杂度&#xff1a;时间复杂度由最高次幂决定(判断大小技巧&#xff1a;将n10代入&#xff09; O(log2 n):二分查找法 O(n&#xff09;:for…