[英]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月,本次“亞特質強制”(或“超特質強制”)的狀態如下:
coerce_inner(
T
)=U
其中T
是的子性狀U
;
還有一個重復的問題#5665 。 那里的評論解釋了是什么阻止了這一點的實施。
+-----+----------------------------------------------+\n | 0- 7|指向“滴膠”功能的指針|\n +-----+----------------------------------------------+\n | 8-15|數據大小|\n +-----+----------------------------------------------+\n |16-23|數據對齊|\n +-----+----------------------------------------------+\n |24- |自我與超特質的方法|\n +-----+----------------------------------------------+\n它不包含作為子序列的超特征的 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.