首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >为什么铁锈不支持特征对象向上投射?

为什么铁锈不支持特征对象向上投射?
EN

Stack Overflow用户
提问于 2015-02-20 15:49:49
回答 4查看 17.2K关注 0票数 71

鉴于这一守则:

代码语言:javascript
运行
复制
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

代码语言:javascript
运行
复制
fn example(v: &Derived) {
    v as &Base;
}
代码语言:javascript
运行
复制
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显示如下:

代码语言:javascript
运行
复制
@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
}

所有锈蚀表都包含一个指向第一个字段中析构函数、大小和对齐方式的指针,并且在引用超级属性方法时,子属性vtable不会复制它们,也不会使用对超级属性vtable的间接引用。它们只有方法指针的副本、逐字指针,而没有其他东西。

考虑到这种设计,很容易理解为什么这不起作用。需要在运行时构造一个新的vtable,它很可能驻留在堆栈中,而这并不是一个优雅(或最佳)的解决方案。

当然,也有一些解决办法,比如向接口中添加显式向上转换方法,但这需要相当多的样板(或宏狂热)才能正常工作。

现在,问题是--为什么它不以某种方式实现,从而使特性对象向上转换?例如,在子属性的vtable中添加指向超属性vtable的指针。目前,Rust的动态调度似乎不能满足Liskov代换原理,这是面向对象设计的一个非常基本的原则。

当然,您可以使用静态分派,这在Rust中确实非常优雅,但它很容易导致代码膨胀,这有时比计算性能更重要--比如在嵌入式系统上,而且Rust开发人员声称支持这种语言的用例。而且,在许多情况下,您可以成功地使用一个不仅仅是面向对象的模型,这似乎受到了Rust的功能设计的鼓励。不过,Rust支持许多有用的OO模式..。那么为什么不是LSP呢?

有谁知道这种设计的原理吗?

EN

回答 4

Stack Overflow用户

发布于 2015-02-22 23:49:28

实际上,我想我知道原因了。我找到了一种优雅的方法,可以为任何想要的特性添加向上转换支持,这样程序员就可以选择是向该特性添加额外的vtable条目,还是不添加,这与C++的虚拟和非虚拟方法类似:优雅和模型正确性与性能。

该守则可按以下方式实现:

代码语言:javascript
运行
复制
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类型的要求),但这是一个普遍的想法。这允许对每种派生类型进行安全和简单的(虽然不是隐式)向上转换,而不对每种派生类型使用样板。

票数 69
EN

Stack Overflow用户

发布于 2015-02-20 16:39:08

我从铁锈开始的时候撞到了同一堵墙。现在,当我想到特质时,我的脑海中有一个与我想到的课程不同的形象。

trait X: Y {}意味着当您为struct S实现属性X时,还需要来为S实现特征Y

当然,这意味着&X知道它也是&Y,因此提供了适当的函数。如果您需要首先遍历指向Y vtable的指针,则需要一些运行时工作(更多指针取消)。

再说一遍,当前的设计+指向其他vtable的附加指针可能不会带来太大的伤害,并且可以实现简单的转换。所以也许我们两者都需要?这是要在internals.rust-lang.org上讨论的问题

票数 22
EN

Stack Overflow用户

发布于 2021-09-15 14:50:23

这个特性是如此的理想,以至于在将它添加到语言中时会出现一个跟踪问题,同时也会为为实现它做出贡献的人员提供一个专用的计划存储库。

追踪问题:https://github.com/rust-lang/rust/issues/65991

主动储存库:https://github.com/rust-lang/dyn-upcasting-coercion-initiative

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/28632968

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档