Rust为什么不支持特质对象上传?

鉴于此代码:

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 。 我想知道为什么,因为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都包含指向析构函数的指针,大小和alignment方式在第一个字段中,当引用supertrait方法时,subtrait vtables不复制它们,也不间接引用supertrait vtables。 他们只是方法指针的副本逐字,没有别的。

鉴于这种devise,很容易理解为什么这不起作用。 一个新的vtable将需要在运行时构build,这可能会驻留在堆栈上,这不完全是一个优雅(或最佳)的解决scheme。

当然有一些解决方法,比如向接口添加显式的upcast方法,但是需要相当多的样板(或者疯狂的疯狂)才能正常工作。

现在的问题是,为什么不以某种方式实现特质对象上传? 就像在减法的vtable中添加一个指向超级用户的vtable的指针。 目前,Rust的dynamic调度似乎并不能满足LSP ,这是面向对象devise的一个非常基本的原则。

当然,你可以使用静态分派,这在Rust中确实非常优雅,但是它很容易导致代码膨胀,这有时比计算性能更重要 – 就像在embedded式系统上一样,Rust开发者声称支持这样的用例语言。 此外,在许多情况下,您可以成功地使用一个不是纯粹的面向对象的模型,这似乎受到了Rust的functiondevise的鼓励。 尽pipe如此,Rust支持许多有用的面向对象的模式……那么为什么不使用LSP呢?

有谁知道这种devise的理由?

其实我觉得我有理由 我发现了一种优雅的方式来添加对任何需要它的特性的上传支持,这样程序员就可以select是否将这个额外的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 (它增加了一个必须是'statictypes的要求),但这是一个普遍的想法。 这允许每个派生types的安全和简单(但不是隐含的)每个派生types的上传,没有样板。

当我开始与Rust时,我碰到了同一堵墙。 现在,当我思考特质的时候,我想起了一个与我思考class级时不同的形象。

trait X : Y {}意味着当你为struct S实现特征X ,你还需要S实现特征Y

当然这意味着一个&X知道它也是一个&Y ,因此提供了相应的function。 如果您需要先将指针传递到Y的vtable,那么需要一些运行时间(更多的指针取消引用)。

再说一次,当前的devise+其他指针指向其他的vtables可能不会有太大的伤害,并且可以轻松实现。 所以也许我们需要两个? 这是要在internal.rust-lang.org上讨论的

截至2017年6月,这种“次级胁迫”(或“超级胁迫胁迫”)的状态如下:

  • 接受的RFC #0401提到这是强制的一部分。 所以这个转换应该隐式完成。

    coerce_inner( T )= U其中TU一个子特征;

  • 但是,这还没有实施。 有一个相应的问题#18600 。

还有一个重复的问题#5665 。 那里的评论解释了什么阻止了这个被实现。

  • 基本上,问题是如何获得超级特性的vtable。 目前vtables的布局如下(在x86-64的情况下):
      + ----- + ------------------------------- +
     |  0〜7 |指向“drop glue”函数|
     + ----- + ------------------------------- +
     |  8-15 |数据大小|
     + ----- + ------------------------------- +
     | 16-23 |数据alignment|
     + ----- + ------------------------------- +
     | 24 | |自拍和超拍的方法|
     + ----- + ------------------------------- +
    

    它不包含作为子序列的超级特质的vtable。 我们至less有一些调整与vtable。

  • 当然有办法可以缓解这个问题,但很多有不同的优点/缺点! 当有钻石inheritance时,其中一个对vtable大小有好处。 另一个应该是更快。

@typelist说他们准备了一个看起来组织良好的RFC草案 ,但看起来像是在那之后消失了(2016年11月)。