一些免费的网站,导购个人网站怎么做的,北京有几个区哪个区最好,怎么做公司网站推广上一篇文章 从其他语言来到Rust的程序员通常习惯于将反思作为工具箱中的工具。他们可能会浪费很多时间试图在Rust中实现基于反射的设计#xff0c;却发现他们所尝试的事情只能做得不好#xff0c;如果有的话。这个项目希望通过描述Rust在反思方面做什么和不做什么#xff0c…上一篇文章 从其他语言来到Rust的程序员通常习惯于将反思作为工具箱中的工具。他们可能会浪费很多时间试图在Rust中实现基于反射的设计却发现他们所尝试的事情只能做得不好如果有的话。这个项目希望通过描述Rust在反思方面做什么和不做什么以及可以使用什么来节省探索死胡同所浪费的时间。
反思是程序在运行时检查自己的能力。给定一个运行时的项目它涵盖了以下问题
关于物品的类型可以确定哪些信息这些信息能做什么
具有全面反思支持的编程语言对这些问题有广泛的答案。基于反射信息带有反射的语言通常在运行时支持以下部分或全部内容
确定变量的类型探索其内容修改其字段调用其方法
具有这种反射支持水平的语言也往往是动态类型语言例如Python、Ruby但也有一些值得注意的静态类型语言也支持反射特别是java和Go。
Rust不支持这种类型的反射这使得避免反射的建议在这个层面上很容易遵循——这是不可能的。对于来自支持充分反思的语言的程序员来说这种缺席起初似乎是一个重大差距但Rust的其他功能提供了解决许多相同问题的替代方法。
C具有更有限的反射形式称为运行时类型识别RTTI。typeid运算符为每个类型返回一个唯一标识符用于多态类型的对象大致具有虚拟函数的类
typeid可以通过基类引用恢复引用的对象的具体类dynamic_castT允许将基类引用转换为派生类当这样做安全且正确时
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::Veci32 [3, 4, 2]该实现由std::any::type_nameT库函数提供该函数也是通用的。此函数只能访问编译时信息没有代码运行来确定运行时的类型。之前中使用的特征对象类型说明了这一点
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_idT: 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提供了一个全面的实现:
implT: 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的指针。对于Anyvtable有一个条目用于返回项目类型的type_id()方法如下图所示
let x_any: Boxdyn Any Box::new(42u64);
let y_any: Boxdyn Any Box::new(Square::new(3, 4, 3)); Any特征对象每个都有指向具体项目和vtable的指针
除了几个间接性外dyn Any特征对象实际上是原始指针和类型标识符的组合。这意味着标准库可以提供一些额外的通用方法这些方法是为dyn Any特征对象定义的这些方法是一些附加类型T的通用的
is::T()指示特征对象的类型是否等于某些特定的其他类型Tdowncast_ref::T()返回对具体类型T的引用前提是特征对象的类型匹配Tdowncast_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用于Draw和Shape
与上一张图表相比很明显显式构造的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特征都没有超特征因此上投的能力不会增加其功能。