为什么Rust不支持特征对象上载? [英] Why doesn't Rust support trait object upcasting?

查看:286
本文介绍了为什么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:

Unfortunately, I cannot cast &Derived to &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方法.

Why is that? The Derived vtable has to reference the Base methods in one way or another.

检查LLVM IR会显示以下内容:

Inspecting the LLVM IR reveals the following:

@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 vtable在第一个字段中都包含一个指向析构函数,大小和对齐方式的指针,并且子特征vtable在引用超特征方法时不会复制它们,也不会使用对超特征vtable的间接引用.他们只是逐字保留了方法指针的副本,而没有其他内容.

All Rust vtables contain a pointer to the destructor, size and alignment in the first fields, and the subtrait vtables don't duplicate them when referencing supertrait methods, nor use indirect reference to supertrait vtables. They just have copies of the method pointers verbatim and nothing else.

鉴于该设计,很容易理解为什么这不起作用.需要在运行时构造一个新的vtable,该vtable可能会驻留在堆栈中,而这并不是一种完美的(或最佳的)解决方案.

Given that design, it's easy to understand why this does not work. A new vtable would need to be constructed at runtime, which would likely reside on the stack, and that isn't exactly an elegant (or optimal) solution.

当然,有一些解决方法,例如向接口添加显式的上载方法,但这需要相当多的样板(或宏狂)才能正常工作.

There are some workarounds, of course, like adding explicit upcast methods to the interface, but that requires quite a bit of boilerplate (or macro frenzy) to work properly.

现在,问题是-为什么不以某种方式实现特征对象转换?就像在子特征的vtable中添加指向超级特征的vtable的指针一样.目前,Rust的动态调度似乎还不能满足 Liskov替代原则,面向对象设计的基本原理.

Now, the question is - why isn't it implemented in some way that would enable trait object upcasting? Like, adding a pointer to the supertrait's vtable in the subtrait's vtable. For now, Rust's dynamic dispatch doesn't seem to satisfy the Liskov substitution principle, which is a very basic principle for object-oriented design.

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

Of course you can use static dispatch, which is indeed very elegant to use in Rust, but it easily leads to code bloat which is sometimes more important than computational performance - like on embedded systems, and Rust developers claim to support such use cases of the language. Also, in many cases you can successfully use a model which is not purely Object-Oriented, which seems to be encouraged by Rust's functional design. Still, Rust supports many of the useful OO patterns... so why not the LSP?

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

Does anyone know the rationale for such design?

推荐答案

实际上,我认为我有原因.我找到了一种优雅的方法,可以向需要的任何特性添加向上转换支持,并且程序员可以选择是否将其他vtable条目添加到特性中,或者不愿意添加,这与C ++的虚拟与非虚拟方法:优雅和模型正确性与性能.

Actually, I think I got the reason. I found an elegant way to add upcasting support to any trait that desires it, and that way the programmer is able to choose whether to add that additional vtable entry to the trait, or prefer not to, which is a similar trade-off as in C++'s virtual vs. non-virtual methods: elegance and model correctness vs. performance.

代码可以实现如下:

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类型的要求),但这是一个普遍的想法.这样就可以对每个派生类型进行安全,简单(尽管不是隐式)的上报,而无需为每个派生类型提供样板.

One may add additional methods for casting a &mut pointer or a Box (that adds a requirement that T must be a 'static type), but this is a general idea. This allows for safe and simple (although not implicit) upcasting of every derived type without boilerplate for every derived type.

这篇关于为什么Rust不支持特征对象上载?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆