意譯解構(gòu)Object Safety for trait
借助【虛表vtable
】對被調(diào)用成員函數(shù)【運(yùn)行時(shí)·內(nèi)存尋址】的作法允許系統(tǒng)編程語言Rust
模仿出OOP
高級(jí)計(jì)算機(jī)語言才具備的【專用·多態(tài)Ad-hoc Polymorphism
】特性。
計(jì)算機(jī)高級(jí)語言中的“多態(tài)”術(shù)語是一個(gè)泛指。它通??杀患?xì)化為
基于繼承關(guān)系的“子類·多態(tài)”?
Subtype Polymorphism
?— 形狀相似而類名不同即是不同。重“名分”輕“事實(shí)”。代表語言JAVA
基于接口抽象的“專用·多態(tài)”?
Ad-hoc Polymorphism
?—?突出不同類型間的共性,淡化類型差異。基于“鴨子類型”的“行·多態(tài)”
Row Polymorphism
?— 類名不同卻形狀相似即是相兼容。重“事實(shí)”輕“名分”。代表語言JS
因?yàn)?code>Rust不支持類繼承,所以它的多態(tài)方式僅收斂于
由【
trait Object
?+?trait method
動(dòng)態(tài)分派】的“專用·多態(tài)”由【Lens設(shè)計(jì)模式 + 過程宏】的“行·多態(tài)”
僅拋磚引玉,就不再展開了。
就Rust
生產(chǎn)實(shí)踐而言,這也是“以時(shí)間換空間”縮小編譯輸出二進(jìn)制文件體積的絕佳手段。其對WEB
匯編技術(shù)方向更是意義深遠(yuǎn),因?yàn)橹灰阅軌蛴茫?code>WASM體積越小越好。對webapp
來講,用過剩的性能換取發(fā)布包更小的體積還是很劃算的。但,rustc
要求凡是參與【專用多態(tài)】抽象的trait
都必須Object Safety
?!皩ο蟀踩钡闹形闹弊g非常令人費(fèi)解。
但結(jié)合【專用多態(tài)】技術(shù)語境,Object Safety
可“啰嗦地”意譯表達(dá)為:“trait method
調(diào)用端不需要對trait
實(shí)現(xiàn)類及其實(shí)例對象有任何了解與假設(shè),而僅憑trait
描述自身,就能順利地尋址和執(zhí)行trait method
,以及獲得trait method
執(zhí)行反饋”。因此,Safety
不是直譯的“安全”,而是意譯的“不知”。
@Rustacean 也可將Object Safety
精煉地領(lǐng)會(huì)為“對象不知”或倒裝一下“不知(類型與)對象(就能執(zhí)行它的成員方法)”。
trait對象安全的核心原則
【專用多態(tài)】抽象要求trait
將其具體實(shí)現(xiàn)類以【動(dòng)態(tài)大小類型DST
】的?Sized
形式呈現(xiàn)給trait method
調(diào)用端。即,胖指針(= 數(shù)據(jù)指針 + 虛表指針)
在編譯時(shí),不鎖定數(shù)據(jù)類型。但因指針大小是固定的,所以編譯操作依舊能夠成功完成。
在運(yùn)行時(shí),實(shí)時(shí)度量變量大小,不論它是【堆】變量
Box<dyn Trait>
,還是【?!孔兞?code>&dyn Trait。
以代碼語言概括之,trait
和(動(dòng)態(tài)分派)trait method
都必須滿足DST
的where Self: ?Sized
限定條件。事實(shí)上,where Self: ?Sized
也是rustc
對trait
自身與trait
關(guān)聯(lián)函數(shù)的默認(rèn)限定。
名詞解釋:
DST
縮寫詞的全稱Dynamic Sized Type
。其含義是“運(yùn)行時(shí)確定大小的數(shù)據(jù)類型”。所以,它的trait
限定條件是?Sized
。
FST
縮寫詞的全稱Fixed Sized Type
。其含義是“編譯時(shí)確定大小的數(shù)據(jù)類型”。所以,它的trait
限定條件是Sized
。
對照【泛型類型參數(shù)】記憶
對照點(diǎn)一:
泛型類型參數(shù)默認(rèn)是
FST
,但可where T: ?Sized
選擇退出默認(rèn)約定trait
與trait method
缺省都是DST
,但同時(shí)也支持where Self: Sized
選擇退出初始限定
對照點(diǎn)二:例程1
泛型類型參數(shù)的
Sized
限定條件是可以被書面重申的,雖然這完全沒有必要。trait
與trait method
定義卻不能書面地限定where Self: ?Sized
。這會(huì)導(dǎo)致編譯失敗,因?yàn)?code>?Sized僅能書面地限定泛型類型參數(shù)(的形參)。
判斷trait是否對象安全的極簡checklist
舊版The Rust Programming Language
教程曾經(jīng)列舉過操作性極強(qiáng)的篩選標(biāo)準(zhǔn):
trait method
返回值類型不是Self
trait method
不是【泛型函數(shù)】
雖至今其仍在互聯(lián)網(wǎng)上廣為流傳,但它對知識(shí)內(nèi)核的過度簡化極易誤導(dǎo) @Rustacean 認(rèn)為Object Safe trait
的全部trait method
都必須是【動(dòng)態(tài)分派】的。其實(shí)不然,對象安全trait
也被允許包含編譯時(shí)【靜態(tài)分派】的成員方法。事實(shí)上,只要trait
自身滿足Object Safety
基本規(guī)則,它的成員方法
既可以被收錄入
vtable
和參與【動(dòng)態(tài)分派】 — 對trait method
隱式類型參數(shù)Self
不做任何限定也能編譯時(shí)被單態(tài)化和參與【靜態(tài)分派】 — 以
where Self: Sized
限定trait method
隱式類型參數(shù)Self
同一個(gè)trait
定義動(dòng)/靜兩用,沒毛??!例程2?走出這個(gè)知識(shí)點(diǎn)誤區(qū)有助于避免在業(yè)務(wù)功能開發(fā)過程中頻繁地“鉆牛角尖”和減輕心智痛苦。
trait自身對象安全的基本原則
-
trait
定義的隱式類型參數(shù)Self
必須是?Sized
的。這也意味著:-
若有
supertrait
,那么supertrait
也必須是?Sized
的,因?yàn)?code>trait Trait: Supertrait {}就是trait Trait where Self: Supertrait {}
的語法糖。例程3// 因?yàn)閌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); // 不可動(dòng)態(tài)分派。
-
若
supertrait
是泛型trait
,那么supertrait
泛型類型參數(shù)的實(shí)參一定不能是Self
,因?yàn)?code>Self編譯時(shí)類型不確定和不能作為單態(tài)化參數(shù)。例程4trait Super<A> {} // 該`trait`不是`Object Safety`的,因?yàn)樗碾[式類型參數(shù)`Self`是`Sized`的。 // - 若抹掉`trait`的`where`從句,那么泛型的【靜態(tài)分派】會(huì)抱怨:“編譯時(shí),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); // 失敗,因?yàn)閌Self: Sized`
-
-
trait
定義不能包含【關(guān)聯(lián)常量】。例程5// 該`trait`不是`Object Safety`的, trait NotObjectSafe { // 因?yàn)樗恕娟P(guān)聯(lián)常量】 const CONST: i32 = 1; } struct S; impl NotObjectSafe for S {} let obj: Box<dyn NotObjectSafe> = Box::new(S);
-
trait
定義中非成員方法【關(guān)聯(lián)函數(shù)】的隱式類型參數(shù)Self
必須被顯式地限定為Sized
?例程6。即,where Self: Sized
。// `trait`不是`Object Safety`,因?yàn)?trait NotObjectSafe { // 它的非成員方法關(guān)聯(lián)函數(shù)的隱式類型參數(shù)`Self`不是`Sized`, // 而是缺省的`?Sized` fn foo() {} } struct S; impl NotObjectSafe for S {} let obj: Box<dyn NotObjectSafe> = Box::new(S); // 編譯失敗
因?yàn)殡[式類型參數(shù)
Self
的缺省限定條件就是?Sized
,所以 @Rustacean 需要利用where
從句書面地退出初始限定和重置Self
為Sized
的。// `trait`是`Object Safety`,因?yàn)?trait NotObjectSafe { // 它的非成員方法關(guān)聯(lián)函數(shù)的隱式類型參數(shù)`Self`被顯式地限定為`Sized`, fn foo() where Self: Sized {} } struct S; impl NotObjectSafe for S {} let obj: Box<dyn NotObjectSafe> = Box::new(S); // 編譯成功
至此,若不考慮trait method
,獲得一個(gè)【對象安全】的trait
并不難。
對象安全trait的成員方法
【重申強(qiáng)調(diào)】即便trait
定義的全部成員方法都不參與【動(dòng)態(tài)分派】(即,與它配對的虛表是空),但只要滿足上節(jié)羅列的三項(xiàng)條件,該trait
依舊是“對象安全”的。只不過,它的trait Object
沒啥實(shí)用意義。
靜態(tài)分派trait method
因?yàn)?code>trait【關(guān)聯(lián)函數(shù)】的缺省抽象形式是【動(dòng)態(tài)分派】,所以 @Rustacean 需要顯式地將trait method
隱式類型參數(shù)Self
限定為Sized
。即,給trait method
聲明添加where Self: Sized
限定條件和退出DST
內(nèi)存布局模式?例程7。然后,你就再也不用擔(dān)心這些trait method
是否是【泛型函數(shù)】
非
self
形參與返回值類型是否是Self
self
參數(shù)數(shù)據(jù)類型
雖然省心了,但胖指針(堆Box<dyn Trait>
或棧&dyn Trait
)也再點(diǎn)不出這些trait method
了。請仔細(xì)閱讀下面例程代碼中的注釋和體會(huì)其中的差別。
// 雖然`trait`是`Object Safety`,
trait Trait {
// (1) 但它的`trait method`都是靜態(tài)分派的,和不能從`Box<dyn Trait>`上被調(diào)用
// — `trait method`的隱式類型參數(shù)`Self`都被顯示地限定為`Sized`的,
// (2) 于是,成員方法的
fn returns(&self) -> Self // a. 返回值類型被允許是`Self`
where Self: Sized;
fn param(&self, other: Self) // b. 非`self`形參也被允許是`Self`數(shù)據(jù)類型
where Self: Sized {}
fn typed<T>(&self, x: T) // c. 接受【泛型函數(shù)】成員方法
where Self: Sized {}
// (3) 非成員方法的關(guān)聯(lián)函數(shù)必須是靜態(tài)分派的
fn foo()
where Self: Sized {} // 手工限定其是靜態(tài)分派函數(shù)
}
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) 它沒有可運(yùn)行時(shí)尋址調(diào)用的成員方法。
// obj.returns(); // 失敗,因?yàn)? where Self: Sized
// (2) 它的`trait method`都必須從實(shí)現(xiàn)類的實(shí)例對象上被調(diào)用
<S as Trait>::foo();
let obj = S {field: 13};
obj.returns();
obj.typed(1);
對象安全的動(dòng)態(tài)分派trait method
雖然【動(dòng)態(tài)分派】是全部trait method
的“天賦技能”,但 @Rustacean 也有義務(wù)從編程環(huán)節(jié)確保trait method
不依賴于trait
實(shí)現(xiàn)類的任何【元信息】。即,trait method
函數(shù)體對trait
實(shí)現(xiàn)類的類型信息不知。
“不知”即是“安全”。“對象安全”還真不如意譯為“對象不知”。這多有趣呀!
在書面代碼上,@Rustacean 僅需要做到在trait method
定義中,
不出現(xiàn)【泛型類型參數(shù)】?例程8。例外,【泛型生命周期參數(shù)】還是被允許的。例程9
非
self
形參與返回值類型不能是Self
。關(guān)鍵字Self
代指trait
實(shí)現(xiàn)類,但Object safe trait
需要對實(shí)現(xiàn)類不知。-
self
形參的數(shù)據(jù)類型必須是如下六種之一?例程10只讀引用
&Self / &self
可修改引用
&mut Self / &mut self
智能指針
Box<Self>
引用計(jì)數(shù)
Rc<Self>
原子引用計(jì)數(shù)
Arc<Self>
不可
swap
內(nèi)存Pin<P>
。其中,泛型類型參數(shù)P
可以是前五種類型中的任意一種。
千萬別限定
trait method
的隱式類型參數(shù)Self
為Sized
。
條條框框還是比較多的,可得常記頻用,才可應(yīng)用自如。
對象安全trait的非成員方法關(guān)聯(lián)函數(shù)
這類associated functions
概念對等于Typescript
的靜態(tài)成員方法?!办o態(tài)”意味著這類關(guān)聯(lián)函數(shù)一定不會(huì)參與動(dòng)態(tài)分派,但出于未知原因rustc
依舊偏好將其收錄虛表vtable
和造成trait Object
實(shí)例化失敗。所以,Object safe trait
的重要原則之一,就是:
要么,沒有非成員方法關(guān)聯(lián)函數(shù)
要么,顯式地書面限定每個(gè)非成員方法關(guān)聯(lián)函數(shù)的隱式類型參數(shù)
Self
為Sized
。例程11
否則,編譯失敗。
// `trait Trait`不是對象安全的,
trait Trait {
// 因?yàn)樗姆浅蓡T方法關(guān)聯(lián)函數(shù)不可動(dòng)態(tài)分派,但還被收錄`vtable`
fn foo() {} // 給加添加`where Self: Sized`限定條件,可解編譯失敗
}
struct S;
impl Trait for S {}
let obj: Box<dyn Trait> = Box::new(S);
結(jié)束語
【動(dòng)態(tài)分派】是trait
和trait method
初始開啟的天賦技能。除了性能極客,@Rustacean 一般想不起刻意地對定它們做靜態(tài)化處理。但,由于項(xiàng)目歷史包袱,在舊trait
定義內(nèi)遺留的
泛型函數(shù)
Self
濫用非成員方法關(guān)聯(lián)函數(shù)
導(dǎo)致其不再“對象安全”。咱們既不必埋怨舊代碼作者(哎!誰的認(rèn)知不是逐步深化的呀),也別慌,更別像我一樣傻乎乎地立即重構(gòu)代碼(很傷的)。而僅只需要將僅能靜態(tài)分派關(guān)聯(lián)函數(shù)的隱式類型參數(shù)Self
限定為Sized
即可。只要虛表不再收錄它們,rustc
就不會(huì)抱怨了。于是,“同一個(gè)trait
既兼容于新/舊代碼,還動(dòng)/靜兩用”豈不美哉?例程12!可是不值得炫技,因?yàn)榇罅窟@類trait
代碼是餒餒的后期維護(hù)心智災(zāi)難 — 只能算是變通的“歪招”。
這次分享的內(nèi)容就是這些。創(chuàng)作不易,希望路過的神仙哥哥、仙女妹妹們評(píng)論、點(diǎn)贊、轉(zhuǎn)發(fā)呀!相信我在【WEB
匯編】技術(shù)方向Rust
棧至今都是最優(yōu)選擇。文章來源:http://www.zghlxwxcb.cn/news/detail-616445.html
文章來源地址http://www.zghlxwxcb.cn/news/detail-616445.html
到了這里,關(guān)于【Rust筆記】意譯解構(gòu) Object Safety for trait的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!