代码之家  ›  专栏  ›  技术社区  ›  Locke

铁锈静态特性转换[复制]

  •  0
  • Locke  · 技术社区  · 7 年前

    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 以某种方式。


    @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需要在运行时构建,它可能驻留在堆栈上,这并不是一个优雅(或最佳)的解决方案。

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

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

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

    0 回复  |  直到 8 年前
        1
  •  51
  •   Shepmaster Tim Diekmann    8 年前

    实际上,我想我知道原因了。我找到了一种优雅的方式来向任何想要的特性添加上行支持,这样程序员就可以选择是否将额外的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 类型),但这是一个一般的想法。这样就可以安全而简单地(尽管不是隐式的)对每个派生类型进行上推,而不必为每个派生类型设置样板。

        2
  •  24
  •   Masaki Hara    9 年前

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

    • +-----+-------------------------------+
      | 0- 7|pointer to "drop glue" function|
      +-----+-------------------------------+
      | 8-15|size of the data               |
      +-----+-------------------------------+
      |16-23|alignment of the data          |
      +-----+-------------------------------+
      |24-  |methods of Self and supertraits|
      +-----+-------------------------------+
      
      它不包含作为子序列的超级特征的vtable。我们至少要对vtables做些调整。
    • 当然,有一些方法可以缓解这个问题,但是很多方法都有不同的优缺点!当有钻石继承时,vtable的大小对它有好处。另一个应该更快。

    在那里 @typelist a draft RFC 看起来组织得很好,但之后就消失了(2016年11月)。

        3
  •  19
  •   Earth Engine    8 年前

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

    trait X: Y {} 意味着当你实现特质的时候 X 对于结构 S 需要 实现特质 Y 对于 S .

    &X 知道它也是一个 &Y ,因此提供了适当的功能。 是的 首先是vtable。

    再说一次,当前的design+指向其他vtable的额外指针可能不会造成太大的伤害,并且允许实现简单的强制转换。所以也许我们两者都需要?这是一件值得讨论的事 internals.rust-lang.org

    推荐文章