用群晖做网站,wordpress图片菜单,一个空间放2个wordpress,虚拟主机可以建设什么网站意译解构Object Safety for trait 借助【虚表vtable】对被调用成员函数【运行时内存寻址】的作法允许系统编程语言Rust模仿出OOP高级计算机语言才具备的【专用多态Ad-hoc Polymorphism】特性。 计算机高级语言中的“多态”术语是一个泛指。它通常可被细化为 基于继承关系的“子… 意译解构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调用端。即胖指针 数据指针 虚表指针 在编译时不锁定数据类型。但因指针大小是固定的所以编译操作依旧能够成功完成。在运行时实时度量变量大小不论它是【堆】变量Boxdyn 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: Boxdyn Trait Box::new(S); // 不可动态分派。若supertrait是泛型trait那么supertrait泛型类型参数的实参一定不能是Self因为Self编译时类型不确定和不能作为单态化参数。例程4trait SuperA {}
// 该trait不是Object Safety的因为它的隐式类型参数Self是Sized的。
// - 若抹掉trait的where从句那么泛型的【静态分派】会抱怨“编译时Self的
// 类型大小未知”。总之左右为难。
trait Trait: SuperSelf where Self: Sized {}
struct S;
implA SuperA for S {}
impl Trait for S {}
let obj: Boxdyn Trait Box::new(S); // 失败因为Self: Sizedtrait定义不能包含【关联常量】。例程5// 该trait不是Object Safety的
trait NotObjectSafe {// 因为它包含了【关联常量】const CONST: i32 1;
}
struct S;
impl NotObjectSafe for S {}
let obj: Boxdyn NotObjectSafe Box::new(S);trait定义中非成员方法【关联函数】的隐式类型参数Self必须被显式地限定为Sized 例程6。即where Self: Sized。// trait不是Object Safety因为
trait NotObjectSafe {// 它的非成员方法关联函数的隐式类型参数Self不是Sized// 而是缺省的?Sizedfn foo() {}
}
struct S;
impl NotObjectSafe for S {}
let obj: Boxdyn NotObjectSafe Box::new(S); // 编译失败因为隐式类型参数Self的缺省限定条件就是?Sized所以 Rustacean 需要利用where从句书面地退出初始限定和重置Self为Sized的。// trait是Object Safety因为
trait NotObjectSafe {// 它的非成员方法关联函数的隐式类型参数Self被显式地限定为Sizedfn foo() where Self: Sized {}
}
struct S;
impl NotObjectSafe for S {}
let obj: Boxdyn 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参数数据类型 虽然省心了但胖指针堆Boxdyn Trait或栈dyn Trait也再点不出这些trait method了。请仔细阅读下面例程代码中的注释和体会其中的差别。 // 虽然trait是Object Safety
trait Trait {// (1) 但它的trait method都是静态分派的和不能从Boxdyn Trait上被调用// — trait method的隐式类型参数Self都被显示地限定为Sized的// (2) 于是成员方法的fn returns(self) - Self // a. 返回值类型被允许是Selfwhere Self: Sized;fn param(self, other: Self) // b. 非self形参也被允许是Self数据类型where Self: Sized {}fn typedT(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: Boxdyn 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智能指针BoxSelf引用计数RcSelf原子引用计数ArcSelf不可swap内存PinP。其中泛型类型参数P可以是前五种类型中的任意一种。千万别限定trait method的隐式类型参数Self为Sized。 条条框框还是比较多的可得常记频用才可应用自如。 对象安全trait的非成员方法关联函数 这类associated functions概念对等于Typescript的静态成员方法。“静态”意味着这类关联函数一定不会参与动态分派但出于未知原因rustc依旧偏好将其收录虚表vtable和造成trait Object实例化失败。所以Object safe trait的重要原则之一就是 要么没有非成员方法关联函数要么显式地书面限定每个非成员方法关联函数的隐式类型参数Self为Sized。例程11 否则编译失败。 // trait Trait不是对象安全的
trait Trait {// 因为它的非成员方法关联函数不可动态分派但还被收录vtablefn foo() {} // 给加添加where Self: Sized限定条件可解编译失败
}
struct S;
impl Trait for S {}
let obj: Boxdyn Trait Box::new(S); 结束语 【动态分派】是trait和trait method初始开启的天赋技能。除了性能极客Rustacean 一般想不起刻意地对定它们做静态化处理。但由于项目历史包袱在旧trait定义内遗留的 泛型函数Self滥用非成员方法关联函数 导致其不再“对象安全”。咱们既不必埋怨旧代码作者哎谁的认知不是逐步深化的呀也别慌更别像我一样傻乎乎地立即重构代码很伤的。而仅只需要将仅能静态分派关联函数的隐式类型参数Self限定为Sized即可。只要虚表不再收录它们rustc就不会抱怨了。于是“同一个trait既兼容于新/旧代码还动/静两用”岂不美哉 例程12可是不值得炫技因为大量这类trait代码是馁馁的后期维护心智灾难 — 只能算是变通的“歪招”。 这次分享的内容就是这些。创作不易希望路过的神仙哥哥、仙女妹妹们评论、点赞、转发呀相信我在【WEB汇编】技术方向Rust栈至今都是最优选择。