性完全指南:從 OOP 繼承思維到 Trait 驅(qū)動的泛型與動態(tài)分發(fā))
Rust 多態(tài)性完全指南從 OOP 繼承思維到 Trait 驅(qū)動的泛型與動態(tài)分發(fā)【免費下載鏈接】comprehensive-rustThis is the Rust course used by the Android team at Google. It provides you the material to quickly teach Rust.項目地址: https://gitcode.com/GitHub_Trending/co/comprehensive-rust本文基于 Google Android 團(tuán)隊的 Rust 培訓(xùn)課程comprehensive-rust中 idiomatic/polymorphism 一章整理而成。該章節(jié)旨在幫助具備 Java、C 等 OOP 背景的開發(fā)者快速理解 Rust 獨特的多態(tài)機(jī)制如何用 trait 表達(dá)行為契約、用泛型實現(xiàn)編譯期單態(tài)化、用dyn Trait實現(xiàn)運行時動態(tài)分發(fā)以及為什么 Rust 拋棄了繼承而選擇組合。讀完本文你將掌握 Rust 多態(tài)代碼的編寫范式、繼承替代方案與異構(gòu)集合的實現(xiàn)思路并能在實際項目中做出正確的泛型與動態(tài)分發(fā)取舍。一、Rust 多態(tài)機(jī)制全景泛型與 trait 對象的交匯Rust 的多態(tài)機(jī)制與 Java、C 等主流語言有明顯差異它沒有繼承、沒有虛函數(shù)表驅(qū)動的默認(rèn)多態(tài)而是圍繞trait 泛型 trait 對象三件套構(gòu)建。課程在 polymorphism.md 中給出了一個濃縮所有核心概念的示例pub trait Trait {} pub struct HasGenericT(T); pub enum EitherA, B { Left(A), Right(B), } fn takes_genericT: Trait(value: T) {} fn takes_dyn(value: dyn Trait) {}這段代碼揭示了 Rust 多態(tài)的兩條核心路徑編譯期多態(tài)靜態(tài)分發(fā)takes_genericT: Trait通過泛型參數(shù)加 trait bound 實現(xiàn)調(diào)用時由編譯器為每個具體類型生成專屬代碼單態(tài)化運行時多態(tài)動態(tài)分發(fā)takes_dyn(value: dyn Trait)通過 trait 對象實現(xiàn)函數(shù)接收的是指向任何實現(xiàn)了Trait的類型的引用具體行為在運行時通過 vtable 解析。HasGenericT與EitherA, B則展示了泛型在類型定義層的應(yīng)用前者是任意單類型包裝后者是經(jīng)典的二選一標(biāo)簽聯(lián)合sum type二者都保持類型信息的靜態(tài)可知性。本章節(jié)接下來將先從 trait 與泛型的基礎(chǔ)Refresher講起再過渡到從 OOP 思維遷移到 Rust 的組合式設(shè)計From OOP to Rust兩部分內(nèi)容分別組織在 refresher.md 與 from-oop-to-rust.md 中。二、復(fù)習(xí)trait 是 Rust 多態(tài)的基石2.1 Traits、協(xié)議與接口編譯期檢查的靜態(tài)鴨子類型Rust 的多態(tài)與泛型體系重度建立在 trait 之上。在 refresher/traits.md 中課程給出了一個直觀的示例定義Receivertrait再讓兩個完全不同的類型分別實現(xiàn)它。trait Receiver { fn send(self, message: str); } struct EmailAddress(String); impl Receiver for EmailAddress { fn send(self, message: str) { println!(Email to {}: {}, self.0, message); } } struct ChatId { uuid: [u8; 16], } impl Receiver for ChatId { fn send(self, message: str) { println!(Chat message sent to {:?}: {}, self.uuid, message); } }課程對 trait 給出了兩個高價值的心智模型靜態(tài)鴨子類型static duck typing鴨子類型源于 Python 等動態(tài)語言——如果它走路像鴨子、叫起來像鴨子那它就是鴨子。trait 同樣指定行為而非具體類型但額外提供了編譯期檢查類型是否真的具備所需行為在編譯時就已被驗證命題與證明propositions and proofstrait 是一組關(guān)于類型的命題集合trait 中的必需方法就是需要履行的義務(wù)為一個類型實現(xiàn) trait補(bǔ)全這些方法就是提交了該類型可用于任何要求此 trait 的場合的證明。2.2 泛型上的 trait bound聲明最小可用行為trait 最常見的用法是作為泛型類型參數(shù)的 bound。在 refresher/trait-bounds.md 中課程解釋了其必要性沒有 trait bound 的泛型參數(shù)無法調(diào)用任何方法因為編譯器不知道類型具備什么行為bound 的作用是聲明一個類型要想在泛型代碼中工作至少需要具備哪些行為。use std::fmt::Display; fn print_with_lengthT: Display(item: T) { println!(Item: {}, item); println!(Length: {}, item.to_string().len()); } fn main() { let number 42; let text Hello, Rust!; print_with_length(number); // 整數(shù)可以 print_with_length(text); // 字符串也可以 }這里T: Display即最小可行行為任何實現(xiàn)了Display的類型都能傳入42與Hello, Rust!均可復(fù)用同一份函數(shù)邏輯。2.3 單態(tài)化編譯期的代碼生成與二進(jìn)制體積權(quán)衡在 refresher/monomorphization.md 中課程揭示了泛型的底層機(jī)制每一個帶泛型的函數(shù)或類型實例都會在編譯時被轉(zhuǎn)換成一份針對具體類型的獨立版本泛型在運行時并不存在存在的只有具體類型。fn print_vecT: std::fmt::Debug(debug_vec: VecT) { for item in debug_vec { println!({:?}, item); } } fn main() { let ints vec![1u32, 2, 3]; let floats vec![1.1f32, 2.2, 3.3]; // 實例一Vecu32 - () print_vec(ints); // 實例二Vecf32 - () print_vec(floats); }單態(tài)化帶來幾點需要權(quán)衡的工程事實性能紅利編譯期就確定了具體類型沒有運行時查找開銷優(yōu)化空間大內(nèi)聯(lián)、常量折疊等都能針對具體類型進(jìn)行成本二進(jìn)制體積與編譯時間會上升按需付費單態(tài)化只針對最終程序或動態(tài)庫中實際使用到的類型實例展開未被使用的泛型實例不會產(chǎn)生代碼何時需要關(guān)注在瀏覽器內(nèi) WebAssembly 或嵌入式系統(tǒng)等體積敏感場景中應(yīng)謹(jǐn)慎設(shè)計泛型的使用方式課程在此章節(jié)未展開體積優(yōu)化的具體手段只提示了影響面。2.4 Sized 與 ?Sized靜態(tài)大小與動態(tài)大小類型在 refresher/sized.md 中課程介紹了區(qū)分編譯期已知大小與運行時已知大小類型的能力use std::fmt::Debug; pub struct AlwaysSizedT /* : Sized */(T); pub struct OptionallySizedT: ?Sized(T); type Dyn1 OptionallySizeddyn Debug;關(guān)鍵規(guī)則Sizedtrait 由所有編譯期已知大小的類型自動實現(xiàn)且默認(rèn)自動添加到每個未顯式退出opt-out的類型參數(shù)上——這就是T默認(rèn)是Sized的原因[T]、str、dyn Trait屬于動態(tài)大小類型DST其大小作為引用的一部分被存儲切片引用含長度trait 對象引用含 vtable 指針只有通過?Sized顯式放寬 bound類型參數(shù)才可能接收 DST。2.5 默認(rèn)方法實現(xiàn)用最小義務(wù)換取完整能力在 refresher/default-impls.md 中課程展示了 trait 設(shè)計中必需方法 默認(rèn)實現(xiàn)的經(jīng)典模式pub trait CollectLeaves { type Leaf; // 必需方法實現(xiàn)者必須提供 fn collect_leaves_buffered(self, buf: mut VecSelf::Leaf); // 默認(rèn)實現(xiàn)基于必需方法派生 fn collect_leaves(self) - VecSelf::Leaf { let mut buf vec![]; self.collect_leaves_buffered(mut buf); buf } }要點方法體存在即為默認(rèn)實現(xiàn)它可基于 trait 內(nèi)其他方法或 supertrait 的方法編寫標(biāo)準(zhǔn)庫中的典型例子是Ord實現(xiàn)者只需提供核心的compare/cmpmax、min、clamp等都以默認(rèn)實現(xiàn)形式給出默認(rèn)方法可以被 derive 宏覆蓋因為 derive 宏會在實現(xiàn)中生成任意 AST。2.6 條件方法實現(xiàn)把約束放在 impl 而非類型定義上在 refresher/conditional-methods.md 中課程介紹了對泛型類型按參數(shù)能力分條件開放方法的寫法// 類型定義上不加任何 trait bound pub struct ValueT(T); // 把 bound 放在 impl 塊上 implT: std::fmt::Display ValueT { fn log(self) { println!({}, self.0); } } // 或者用 where 子句 implT ValueT { fn log_error(self) where T: std::error::Error, { eprintln!({}, self.0); } }課程強(qiáng)調(diào)這是對有序集合等希望內(nèi)部類型總是Ord場景的首選做法不要把這些約束放在類型定義上否則該類型每次以泛型參數(shù)出現(xiàn)時都會被迫承擔(dān)約束影響下游所有使用點把約束放在 impl 上既能維持不變量又不會污染類型定義本身。上面代碼中的兩種寫法等價where子句在約束復(fù)雜時更易讀。三、從 OOP 到 Rust為什么沒有繼承以及替代方案3.1 OOP 繼承速覽在 from-oop-to-rust/inheritance.md 中課程用一段 C 代碼幫助讀者回顧傳統(tǒng)繼承// 基類 class Vehicle { public: void accelerate() { } void brake() { } }; // 繼承類 class Car : public Vehicle { public: void honk() { } }; int main() { Car myCar; // 創(chuàng)建 Car 對象 myCar.accelerate(); // 繼承的方法 myCar.honk(); // Car 自己的方法 myCar.brake(); // 繼承的方法 return 0; }繼承的機(jī)制要點子類型獲得父類型的字段與方法方法可按需被重寫子類可通過super調(diào)用父類方法。繼承是 OOP 范式成功的關(guān)鍵幾十年來無數(shù)業(yè)務(wù)邏輯圍繞它構(gòu)建——這也是 from-oop-to-rust.md 提出的問題顯得尖銳的原因既然繼承如此成功為什么 Rust 要回避它3.2 繼承的三大缺陷from-oop-to-rust/why-no-inheritance.md 給出了 Rust 拒絕繼承的理由并用一段 Rust 代碼直觀對比了想要繼承與用組合實現(xiàn)的寫法差異pub struct Id { pub id: u32 } impl Id { // 方法 } // ? Rust 沒有繼承 // pub struct Data: Id { // pub name: String, // } // ? 組合把 Id 作為字段 pub struct Data { pub id: Id, pub name: String, } impl Data { // Data 自身的、非來自 trait 的方法 } impl SomeTrait for Data { // trait 實現(xiàn)放在獨立的 impl 塊中 }課程列出的繼承三大缺陷默認(rèn)異構(gòu)Heterogeneous by default類繼承隱式允許不同類的類型互換使用卻無法指定具體類型或判斷兩個類型是否相同。在相等性、比較等操作上這會導(dǎo)致運行時拋錯甚至 panic 的相等比較數(shù)據(jù)結(jié)構(gòu)的多事實來源類型字段被繼承層級遮蔽方法可能覆蓋父類或被子類覆蓋——在多方維護(hù)的復(fù)雜代碼庫中很難判斷一個類型的真實行為默認(rèn)動態(tài)分發(fā)帶來 vtable 開銷動態(tài)分發(fā)需要一處存儲該調(diào)用哪個方法等運行時信息即值的vtable。方法調(diào)用因此比編譯期已知類型的直接調(diào)用多出若干次解引用。3.3 組合優(yōu)于繼承用字段拼裝類型from-oop-to-rust/composition.md 展示了替代方案——不是 mixin 也不是繼承而是通過創(chuàng)建不同類型字段來組合類型pub struct Uuid([u8; 16]); pub struct Address { street: String, city_or_province: String, code: String, country: String, } pub struct User { id: Uuid, address: Address, }課程對組合的評述非常務(wù)實優(yōu)點開發(fā)者對類型做什么、能訪問什么擁有完全的控制力與清晰度缺點字段訪問的易用性有所下降需要多一層.派生 trait 時的注意事項使用 derive 宏時務(wù)必確保 struct 的所有字段類型或 enum 的所有變體類型都已實現(xiàn)對應(yīng) trait——derive 宏通常假設(shè)組成新類型的各個成員類型已經(jīng)實現(xiàn)了該 trait。四、實踐建議如何在項目中正確選擇多態(tài)方案結(jié)合課程 polymorphism.md 及各子章節(jié)可以將 Rust 多態(tài)方案的選擇歸納為以下決策要點異構(gòu)集合用 trait 對象需要在一個容器如VecBoxdyn Trait中存放多種類型時使用dyn Trait課程在 from-oop-to-rust.md 中將如何在 Rust 中表示異構(gòu)集合列為核心議題之一熱點代碼優(yōu)先泛型追求性能、希望內(nèi)聯(lián)與靜態(tài)優(yōu)化時用帶 trait bound 的泛型接受單態(tài)化帶來的編譯時間與二進(jìn)制體積成本monomorphization.md體積敏感場景警惕泛型WebAssembly、嵌入式開發(fā)中注意泛型實例的展開數(shù)量同上約束優(yōu)先放 impl泛型類型的約束盡量通過條件方法實現(xiàn)表達(dá)避免污染類型定義conditional-methods.md復(fù)用行為用默認(rèn)實現(xiàn)設(shè)計 trait 時用必需方法 基于它的默認(rèn)實現(xiàn)降低實現(xiàn)者負(fù)擔(dān)default-impls.md領(lǐng)域建模用組合用字段組合替代繼承層級保持類型事實來源單一composition.md。五、延伸閱讀本主題在課程中位于 idiomatic/polymorphism.md 一章其下還有更多進(jìn)階小節(jié)如dynamic-dispatch目錄下的dyn-vs-generics、heterogeneous、dyn-compatible以及sealed-traits、supertraits、blanket-impls、orphan-rule等。在本倉庫中可以繼續(xù)研讀復(fù)習(xí)部分全部小節(jié)refresher.mdtraits、trait-bounds、monomorphization、sized、default-impls、conditional-methods 等OOP 遷移部分全部小節(jié)from-oop-to-rust.mdwhy-no-inheritance、composition、problem-solving、switch-perspective、sealed-traits 等若想系統(tǒng)補(bǔ)強(qiáng)泛型與 trait 基礎(chǔ)可配合課程早期章節(jié) generics 與 methods-and-traits 一起學(xué)習(xí)。【免費下載鏈接】comprehensive-rustThis is the Rust course used by the Android team at Google. It provides you the material to quickly teach Rust.項目地址: https://gitcode.com/GitHub_Trending/co/comprehensive-rust創(chuàng)作聲明:本文部分內(nèi)容由AI輔助生成(AIGC),僅供參考