gpt4 book ai didi

oop - 为什么 Rust 不支持 trait 对象向上转换?

转载 作者:行者123 更新时间:2023-12-03 11:46:56 26 4
gpt4 key购买 nike

鉴于此代码:

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 虚表在第一个字段中都包含指向析构函数、大小和对齐方式的指针,并且子特征虚表在引用超特征方法时不会复制它们,也不会使用对超特征虚表的间接引用。他们只是逐字复制方法指针,没有别的。

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

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

现在,问题是 - 为什么不以某种方式实现特征对象向上转换?比如,在 subtrait 的 vtable 中添加一个指向 supertrait 的 vtable 的指针。目前,Rust 的动态调度似乎不满足 Liskov substitution principle ,这是面向对象设计的一个非常基本的原则。

当然你可以使用静态调度,这在 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 类型的要求),但这是一个普遍的想法。这允许安全和简单(尽管不是隐式)向上转换每个派生类型,而无需为每个派生类型提供样板。

关于oop - 为什么 Rust 不支持 trait 对象向上转换?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65574525/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com