Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/oop/2.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/7/rust/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Oop 为什么';不生锈的支持特征对象向上投射?_Oop_Rust_Language Design_Liskov Substitution Principle - Fatal编程技术网

Oop 为什么';不生锈的支持特征对象向上投射?

Oop 为什么';不生锈的支持特征对象向上投射?,oop,rust,language-design,liskov-substitution-principle,Oop,Rust,Language Design,Liskov Substitution Principle,鉴于此代码: 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) {}

鉴于此代码:

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]:非基元强制转换:`&Derived`as`&Base`
-->src/main.rs:30:5
|
30 | v组件和底座;
|     ^^^^^^^^^^
|
=注意:`as`表达式只能用于在基元类型之间转换。考虑使用“从”特性
为什么呢?
派生的
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在第一个字段中都包含一个指向析构函数、大小和对齐方式的指针,subtrait vtable在引用supertrait方法时不会复制它们,也不会间接引用supertrait vtables。它们只有方法指针的逐字副本,没有其他内容

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

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

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

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


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

当我开始生锈的时候,我碰到了同一堵墙。 现在,当我想到特质时,我脑海中的形象与我想到班级时不同

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

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


再说一次,当前的设计+指向其他vtable的额外指针可能不会有太大的影响,并且可以实现轻松的强制转换。也许我们两个都需要?这是一个需要讨论的问题。事实上,我想我知道原因了。我发现了一种优雅的方法,可以向任何需要的特性添加向上转换支持,这样程序员就可以选择是向该特性添加额外的vtable条目,还是不添加,这与C++的虚拟与非虚拟方法中的权衡类似:优雅、模型正确性与性能

代码可以按如下方式实现:

trait Base: AsBase {
    // ...
}

trait AsBase {
    fn as_base(&self) -> &Base;
}

impl<T: Base> AsBase for T {
    fn as_base(&self) -> &Base {
        self
    }
}
trait Base:AsBase{
// ...
}
性状AsBase{
fn as_base(&self)->&base;
}
用于T的impl AsBase{
fn as_base(&self)->&base{
自己
}
}

可以添加其他方法来强制转换
&mut
指针或
(这增加了
T
必须是
'静态
类型的要求),但这是一个一般想法。这允许安全、简单(尽管不是隐式)地向上投射每个派生类型,而不必为每个派生类型提供样板。

截至2017年6月,这种“子特质强制”(或“超级特质强制”)的状态如下:

trait Base: AsBase {
    // ...
}

trait AsBase {
    fn as_base(&self) -> &Base;
}

impl<T: Base> AsBase for T {
    fn as_base(&self) -> &Base {
        self
    }
}
  • 一个公认的RFC提到这是胁迫的一部分。因此,这种转换应该隐式进行。 强制内部(
    T
    )=
    U
    ,其中
    T
    U
    的一个子特征

  • 然而,这一点尚未实施。有一个相应的问题
还有一个重复的问题。那里的评论解释了是什么阻止了这一计划的实施

  • 基本上,问题是如何为超级性状导出vtable。vtables的当前布局如下(在x86-64情况下): +-----+-------------------------------+ |0-7 |指向“滴胶”功能的指针| +-----+-------------------------------+ |8-15 |数据的大小| +-----+-------------------------------+ |16-23 |数据对齐| +-----+-------------------------------+ |24-|自我和超特质的方法| +-----+-------------------------------+ 它不包含超级特征的vtable作为子序列。我们至少需要对vtables进行一些调整
  • 当然,有很多方法可以缓解这个问题,但很多方法都有不同的优缺点!当存在菱形继承时,vtable大小有一个好处。另一个应该更快

有人说他们准备了一种看起来组织良好的语言,但在那之后它们似乎消失了(2016年11月)。

作为旁注:Rust不是面向对象的语言。trait不是接口,它们更像来自Haskell的类型类。Rust也没有子类型,因此LSP在某种程度上不适用于它,因为它的定义与子类型关系有关。尽管如此,正如我所说,Rust支持很多OO风格的抽象,并且允许特征继承,形成类似于类型层次结构的东西。对我来说,支持trait对象的LSP似乎是很自然的,即使OO不是语言的主要范例