rust为什么显示不了国服_捋捋 Rust 中的 impl Trait 和 dyn Trait
緣起
一切都要從年末換工作碰上特殊時(shí)期, 在家閑著無(wú)聊又讀了幾首詩(shī), 突然想寫(xiě)一個(gè)可以瀏覽和背誦詩(shī)詞的 TUI 程序說(shuō)起. 我選擇了 Cursive 這個(gè) Rust TUI 庫(kù). 在實(shí)現(xiàn)時(shí)有這么一個(gè)函數(shù), 它會(huì)根據(jù)參數(shù)的不同返回某個(gè)組件(如 Button, TextView 等). 在 Cursive 中, 每個(gè)組件都實(shí)現(xiàn)了 View 這個(gè) trait, 最初這個(gè)函數(shù)只會(huì)返回某個(gè)確定的組件, 所以函數(shù)簽名可以這樣寫(xiě)
fn some_fn(param: SomeType) -> Button隨著開(kāi)發(fā)進(jìn)度增加, 這個(gè)函數(shù)需要返回 Button, TextView 等組件中的一個(gè), 我下意識(shí)地寫(xiě)出了類似于下面的代碼
fn some_fn(param1: i32, param2: i32) -> impl View {if param1 > param2 {// do something...return Button {};} else {// do something...return TextView {};} }可惜 Rust 編譯器一如既往地打臉, Rust 編譯器報(bào)錯(cuò)如下
--> srcmain.rs:19:16| 13 | fn some_fn(param1: i32, param2: i32) -> impl View {| --------- expected because this return type... ... 16 | return Button {};| --------- ...is found to be `Button` here ... 19 | return TextView {};| ^^^^^^^^^^^ expected struct `Button`, found struct `TextView`error: aborting due to previous errorFor more information about this error, try `rustc --explain E0308`.從編譯器報(bào)錯(cuò)信息看函數(shù)返回值雖然是 impl View 但其從 if 分支推斷返回值類型為 Button 就不再接受 else 分支返回的 TextView. 這與 Rust 要求 if else 兩個(gè)分支的返回值類型相同的特性一致. 那能不能讓函數(shù)返回多種類型呢? Rust 之所以要求函數(shù)不能返回多種類型是因?yàn)?Rust 在需要在 編譯期確定返回值占用的內(nèi)存大小, 顯然不同類型的返回值其內(nèi)存大小不一定相同. 既然如此, 把返回值裝箱, 返回一個(gè)胖指針, 這樣我們的返回值大小可以確定了, 這樣也許就可以了吧. 嘗試把函數(shù)修改成如下形式:
fn some_fn(param1: i32, param2: i32) -> Box<View> {if param1 > param2 {// do something...return Box::new(Button {});} else {// do something...return Box::new(TextView {});} }現(xiàn)在代碼通過(guò)編譯了, 但如果使用 Rust 2018, 你會(huì)發(fā)現(xiàn)編譯器會(huì)拋出警告:
warning: trait objects without an explicit `dyn` are deprecated--> srcmain.rs:13:45| 13 | fn some_fn(param1: i32, param2: i32) -> Box<View> {| ^^^^ help: use `dyn`: `dyn View`|= note: `#[warn(bare_trait_objects)]` on by default編譯器告訴我們使用 trait object 時(shí)不使用 dyn 的形式已經(jīng)被廢棄了, 并且還貼心的提示我們把 Box<View> 改成 Box<dyn View>, 按編譯器的提示修改代碼, 此時(shí)代碼 no warning, no error, 完美.
但 impl Trait 和 Box<dyn Trait> 除了允許多種返回值類型的之外還有什么區(qū)別嗎? trait object 又是什么? 為什么 Box<Trait> 形式的返回值會(huì)被廢棄而引入了新的 dyn 關(guān)鍵字呢?
埋坑
impl Trait 和 dyn Trait 在 Rust 分別被稱為靜態(tài)分發(fā)和動(dòng)態(tài)分發(fā). 在第一版的 Rust Book 這樣解釋分發(fā)(dispatch)
When code involves polymorphism, there needs to be a mechanism to determine which specific version is actually run. This is called ‘dispatch’. There are two major forms of dispatch: static dispatch and dynamic dispatch. While Rust favors static dispatch, it also supports dynamic dispatch through a mechanism called ‘trait objects’.即當(dāng)代碼涉及多態(tài)時(shí), 需要某種機(jī)制決定實(shí)際調(diào)用類型. Rust 的 Trait 可以看作某些具有通過(guò)特性類型的集合, 以上面代碼為例, 在寫(xiě)代碼時(shí)我們不關(guān)心具體類型, 但在編譯或運(yùn)行時(shí)必須確定 Button 還是 TextView. 靜態(tài)分發(fā), 正如靜態(tài)類型語(yǔ)言的"靜態(tài)"一詞說(shuō)明的, 在編譯期就確定了具體調(diào)用類型. Rust 編譯器會(huì)通過(guò)單態(tài)化(Monomorphization) 將泛型函數(shù)展開(kāi).
假設(shè) Foo 和 Bar 都實(shí)現(xiàn)了 Noop 特性, Rust 會(huì)把函數(shù)
fn x(...) -> impl Noop展開(kāi)為
fn x_for_foo(...) -> Foo fn x_for_bar(...) -> Bar(僅作原理說(shuō)明, 不保證編譯會(huì)這樣展開(kāi)函數(shù)名).
通過(guò)單態(tài)化, 編譯器消除了泛型, 而且沒(méi)有性能損耗, 這也是 Rust 提倡的形式, 缺點(diǎn)是過(guò)多展開(kāi)可能會(huì)導(dǎo)致編譯生成的二級(jí)制文件體積過(guò)大, 這時(shí)候可能需要重構(gòu)代碼.
靜態(tài)分發(fā)雖然有很高的性能, 但在文章開(kāi)頭其另一個(gè)缺點(diǎn)也有所體現(xiàn), 那就是無(wú)法讓函數(shù)返回多種類型, 因此 Rust 也支持通過(guò) trait object 實(shí)現(xiàn)動(dòng)態(tài)分發(fā). 既然 Trait 是具有某種特性的類型的集合, 那我們可以把 Trait 也看作某種類型, 但它是"抽象的", 就像 OOP 中的抽象類或基類, 不能直接實(shí)例化.
Rust 的 trait object 使用了與 c++ 類似的 vtable 實(shí)現(xiàn), trait object 含有1個(gè)指向?qū)嶋H類型的 data 指針, 和一個(gè)指向?qū)嶋H類型實(shí)現(xiàn) trait 函數(shù)的 vtable, 以此實(shí)現(xiàn)動(dòng)態(tài)分發(fā). 更加詳細(xì)的介紹可以在
Exploring Dynamic Dispatch in Rust?alschwalm.com看到. 既然 trait object 在實(shí)現(xiàn)時(shí)可以確定大小, 那為什么不用 fn x() -> Trait 的形式呢? 雖然 trait object 在實(shí)現(xiàn)上可以確定大小, 但在邏輯上, 因?yàn)?Trait 代表類型的集合, 其大小無(wú)法確定. 允許 fn x() -> Trait 會(huì)導(dǎo)致語(yǔ)義上的不和諧. 那 fn x() -> &Trait 呢? 當(dāng)然可以! 但鑒于這種場(chǎng)景下都是在函數(shù)中創(chuàng)建然后返回該值的引用, 顯然需要加上生命周期:
fn some_fn(param1: i32, param2: i32) -> &'static View {if param1 > param2 {// do something...return &Button {};} else {// do something...return &TextView {};} }我不喜歡添加額外的生命周期說(shuō)明, 想必各位也一樣. 所以我們可以用擁有所有權(quán)的 Box 智能指針避免煩人的生命周期說(shuō)明. 至此 Box<Trait> 終于出現(xiàn)了. 那么問(wèn)題來(lái)了, 為什么編譯器會(huì)提示 Box<Trait> 會(huì)被廢棄, 特地引入了 dyn 關(guān)鍵字呢? 答案可以在 RFC-2113 中找到.
RFC-2113 明確說(shuō)明了引入 dyn 的原因, 即語(yǔ)義模糊, 令人困惑, 原因在于沒(méi)有 dyn 讓 Trait 和 trait objects 看起來(lái)完全一樣, RFC 列舉了3個(gè)例子說(shuō)明.
第一個(gè)例子, 加入你看到下面的代碼, 你知道作者要干什么嗎?
impl SomeTrait for AnotherTrait impl<T> SomeTrait for T where T: Another你看懂了嗎? 說(shuō)實(shí)話我也看不懂 : ) PASS
第二個(gè)例子, impl MyTrait {} 是正確的語(yǔ)法, 不過(guò)這樣會(huì)讓人以為這會(huì)在 Trait 上添加默認(rèn)實(shí)現(xiàn), 擴(kuò)展方法或其他 Trait 自身的一些操作. 實(shí)際上這是在 trait object 上添加方法.
如在下面代碼說(shuō)明的, Trait 默認(rèn)實(shí)現(xiàn)的正確定義方法是在定義 Trait 時(shí)指定, 而不應(yīng)該在 impl Trait {} 語(yǔ)句塊中.
trait Foo {fn default_impl(&self) {println!("correct impl!");} }impl Foo {fn trait_object() {println!("trait object impl");} }struct Bar {}impl Foo for Bar {}fn main() {let b = Bar{};b.default_impl();// b.trait_object();Foo::trait_object(); }Bar 在實(shí)現(xiàn)了 Foo 后可以通過(guò) b.default_impl 調(diào)用, 無(wú)需額外實(shí)現(xiàn), 但 b.trait_object 則不行, 因?yàn)?trait_object 方法是 Foo 的 trait object 上的方法.
如果是 Rust 2018 編譯器應(yīng)該還會(huì)顯示一條警告, 告訴我們應(yīng)該使用 impl dyn Foo {}
第三個(gè)例子則以函數(shù)類型和函數(shù) trait 作對(duì)比, 兩者差別只在于首字母是否大寫(xiě)(Fn代表函數(shù)trait object, fn則是函數(shù)類型), 難免會(huì)把兩者弄混.
更加詳細(xì)的說(shuō)明可以移步
RFC-2113?github.com.
總結(jié)
impl trait 和 dyn trait 區(qū)別在于靜態(tài)分發(fā)于動(dòng)態(tài)分發(fā), 靜態(tài)分發(fā)性能 好, 但大量使用有可能造成二進(jìn)制文件膨脹; 動(dòng)態(tài)分發(fā)以 trait object 的概念通過(guò)虛表實(shí)現(xiàn), 會(huì)帶來(lái)一些運(yùn)行時(shí)開(kāi)銷. 又因 trait object 與 Trait 在不引入 dyn 的情況下經(jīng)常導(dǎo)致語(yǔ)義混淆, 所以 Rust 特地引入 dyn 關(guān)鍵字, 在 Rust 2018 中已經(jīng)穩(wěn)定.
引用
以下是本文參考的資料
impl Trait for returning complex types with ease?doc.rust-lang.orgimpl trait 社區(qū)跟蹤?github.comrust-lang/rfcs?github.comTraits and Trait Objects in Rust?joshleeb.comDynamic vs. Static Dispatch?lukasatkinson.deExploring Dynamic Dispatch in Rust?alschwalm.comPS: 題圖為盧浦大橋, 全上海我最喜歡的大橋, 沒(méi)有之一~
總結(jié)
以上是生活随笔為你收集整理的rust为什么显示不了国服_捋捋 Rust 中的 impl Trait 和 dyn Trait的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: redis key失效的事件_《分享几道
- 下一篇: 电容屏物体识别_相比传统的触摸屏,电容式