簡體   English   中英

為什么 Rust 不支持 trait 對象向上轉換?

[英]Why doesn't Rust support trait object upcasting?

鑒於此代碼:

trait Base {
    fn a(&self);
    fn b(&self);
    fn c(&self);
    fn d(&self);
}

trait Derived : Base {
    fn e(&self);
    fn f(&self);
    fn g(&self);
}

struct S;

impl Derived for S {
    fn e(&self) {}
    fn f(&self) {}
    fn g(&self) {}
}

impl Base for S {
    fn a(&self) {}
    fn b(&self) {}
    fn c(&self) {}
    fn d(&self) {}
}

不幸的是,我無法將&Derived&Base

fn example(v: &Derived) {
    v as &Base;
}
error[E0605]: non-primitive cast: `&Derived` as `&Base`
  --> src/main.rs:30:5
   |
30 |     v as &Base;
   |     ^^^^^^^^^^
   |
   = note: an `as` expression can only be used to convert between primitive types. Consider using the `From` trait

這是為什么? Derived vtable 必須以一種或另一種方式引用Base方法。


檢查 LLVM IR 會發現以下內容:

@vtable4 = internal unnamed_addr constant {
    void (i8*)*,
    i64,
    i64,
    void (%struct.S*)*,
    void (%struct.S*)*,
    void (%struct.S*)*,
    void (%struct.S*)*
} {
    void (i8*)* @_ZN2i813glue_drop.98717h857b3af62872ffacE,
    i64 0,
    i64 1,
    void (%struct.S*)* @_ZN6S.Base1a20h57ba36716de00921jbaE,
    void (%struct.S*)* @_ZN6S.Base1b20h3d50ba92e362d050pbaE,
    void (%struct.S*)* @_ZN6S.Base1c20h794e6e72e0a45cc2vbaE,
    void (%struct.S*)* @_ZN6S.Base1d20hda31e564669a8cdaBbaE
}

@vtable26 = internal unnamed_addr constant {
    void (i8*)*,
    i64,
    i64,
    void (%struct.S*)*,
    void (%struct.S*)*,
    void (%struct.S*)*,
    void (%struct.S*)*,
    void (%struct.S*)*,
    void (%struct.S*)*,
    void (%struct.S*)*
} {
    void (i8*)* @_ZN2i813glue_drop.98717h857b3af62872ffacE,
    i64 0,
    i64 1,
    void (%struct.S*)* @_ZN9S.Derived1e20h9992ddd0854253d1WaaE,
    void (%struct.S*)* @_ZN9S.Derived1f20h849d0c78b0615f092aaE,
    void (%struct.S*)* @_ZN9S.Derived1g20hae95d0f1a38ed23b8aaE,
    void (%struct.S*)* @_ZN6S.Base1a20h57ba36716de00921jbaE,
    void (%struct.S*)* @_ZN6S.Base1b20h3d50ba92e362d050pbaE,
    void (%struct.S*)* @_ZN6S.Base1c20h794e6e72e0a45cc2vbaE,
    void (%struct.S*)* @_ZN6S.Base1d20hda31e564669a8cdaBbaE
}

所有 Rust vtables 在第一個字段中都包含一個指向析構函數、大小和對齊方式的指針,並且 subtrait vtables 在引用 supertrait 方法時不會復制它們,也不使用對 supertrait vtables 的間接引用。 他們只是逐字復制了方法指針,沒有別的。

鑒於這種設計,很容易理解為什么這不起作用。 需要在運行時構建一個新的 vtable,它可能駐留在堆棧中,這並不是一個優雅(或最佳)的解決方案。

當然,有一些解決方法,比如向接口添加顯式向上轉換方法,但這需要相當多的樣板(或宏狂熱)才能正常工作。

現在,問題是 - 為什么不以某種方式實現它來啟用 trait 對象向上轉換? 比如,在 subtrait 的 vtable 中添加一個指向 supertrait 的 vtable 的指針。 目前,Rust 的動態調度似乎並不滿足Liskov 替換原則,這是面向對象設計的一個非常基本的原則。

當然你可以使用靜態調度,這在 Rust 中使用確實非常優雅,但它很容易導致代碼膨脹,這有時比計算性能更重要——比如在嵌入式系統上,Rust 開發人員聲稱支持這樣的用例語。 此外,在許多情況下,您可以成功地使用不完全面向對象的模型,這似乎受到 Rust 功能設計的鼓勵。 盡管如此,Rust 仍然支持許多有用的 OO 模式……那么為什么不支持 LSP?

有誰知道這種設計的基本原理?

其實我覺得我是有原因的。 我找到了一種優雅的方法來為任何需要它的特征添加向上轉換支持,這樣程序員就可以選擇是否將額外的 vtable 條目添加到特征中,或者不喜歡,這與C++ 的虛擬與非虛擬方法:優雅和模型正確性與性能。

代碼可以實現如下:

trait Base: AsBase {
    // ...
}

trait AsBase {
    fn as_base(&self) -> &Base;
}

impl<T: Base> AsBase for T {
    fn as_base(&self) -> &Base {
        self
    }
}

可以添加其他方法來轉換&mut指針或Box (這增加了T必須是'static類型'static的要求),但這是一個通用的想法。 這允許對每個派生類型進行安全和簡單(盡管不是隱式)向上轉換,而無需為每個派生類型提供樣板。

截至2017年6月,本次“亞特質強制”(或“超特質強制”)的狀態如下:

  • 一個被接受的 RFC #0401提到這是強制的一部分。 所以這個轉換應該隱式完成。

    coerce_inner( T )= U其中T是的子性狀U ;

  • 但是,這尚未實施。 有一個相應的問題#18600

還有一個重復的問題#5665 那里的評論解釋了是什么阻止了這一點的實施。

  • 基本上,問題是如何為超特征導出 vtable。 vtables 的當前布局如下(在 x86-64 情況下):
      +-----+----------------------------------------------+\n |  0- 7|指向“滴膠”功能的指針|\n +-----+----------------------------------------------+\n |  8-15|數據大小|\n +-----+----------------------------------------------+\n |16-23|數據對齊|\n +-----+----------------------------------------------+\n |24- |自我與超特質的方法|\n +-----+----------------------------------------------+\n
    它不包含作為子序列的超特征的 vtable。 我們至少需要對 vtable 進行一些調整。
  • 當然有一些方法可以緩解這個問題,但許多方法具有不同的優點/缺點! 當存在菱形繼承時,vtable 大小有好處。 另一個應該更快。

@typelist說他們准備了一份看起來組織良好的RFC 草案,但在那之后(2016 年 11 月)他們看起來就消失了。

當我開始使用 Rust 時,我遇到了同一堵牆。 現在,當我想到特質時,我腦海中的形象與我想到課程時的形象不同。

trait X: Y {}意味着當你為 struct S實現 trait X ,你還需要S實現 trait Y

當然,這意味着&X知道它也是&Y ,因此提供了適當的功能。 如果您需要首先遍歷指向Y的 vtable 的指針,則需要一些運行時工作(更多的指針取消引用)。

再說一次,當前的設計 + 指向其他 vtable 的附加指針可能不會有太大影響,並且可以輕松實現強制轉換。 所以也許我們兩者都需要? 這是要在internals.rust-lang.org上討論的內容

此功能非常受歡迎,以至於將其添加到語言中存在跟蹤問題,以及為實現它做出貢獻的人員的專用倡議存儲庫。

跟蹤問題: https : //github.com/rust-lang/rust/issues/65991

倡議庫: https : //github.com/rust-lang/dyn-upcasting-coercion-initiative

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM