【问题标题】:How do Rust's Trait Objects handle methods which moves the instance?Rust 的特征对象如何处理移动实例的方法?
【发布时间】:2021-09-04 02:48:44
【问题描述】:

以下是我对 Rust 方法如何工作的基本假设:

foo.method();

其中method 定义为method(&self)fooFoo, 的实例,与

Foo::method(&foo);

我对 Trait Objects 的理解是一个结构体,它有两个 void 指针,一个指向数据,另一个指向函数指针(vtable)

接收一个 Trait 对象并调用该 Trait 对象上的方法的多态函数将编译为查看该方法在 Trait 对象中的偏移量并传入数据指针

但是如果方法移动了实例呢?如果我错了,请纠正我,但要调用虚拟移动方法,该函数必须将存储在特征对象中的实际数据推送到堆栈上,而不仅仅是数据指针。在编译时显然无法知道数据大小,那么这里发生了什么?这是一种 VLA 的情况,还是我误解了移动的工作原理?

【问题讨论】:

    标签: methods rust polymorphism vtable


    【解决方案1】:

    答案很简单——不可能在 trait 对象上调用自消费方法。

    关键词是object safety。本质上,这个属性结合了两个要求:

    • 每个方法都必须通过某种间接方式与self 一起使用;
    • 每个方法都必须通过原始类型完全可识别(即没有泛型)。

    为了更详细地了解这一点,让我们实际尝试编写一些代码并询问编译器的意见。首先,只是尝试定义特征:

    trait Consumer {
        fn consume(self);
    }
    

    编译器已经不高兴了:

    error[E0277]: the size for values of type `Self` cannot be known at compilation time
     --> src/lib.rs:2:16
      |
    2 |     fn consume(self);
      |                ^^^^ doesn't have a size known at compile-time
      |
    help: consider further restricting `Self`
      |
    2 |     fn consume(self) where Self: Sized;
      |                      ^^^^^^^^^^^^^^^^^
    help: function arguments must have a statically known size, borrowed types always have a known size
      |
    2 |     fn consume(&self);
      |                ^
    

    好的,我们可以比编译器的建议更保守,并添加对 trait 的限制。然后,为 trait 对象创建添加一个存根:

    trait Consumer where Self: Sized {
        fn consume(self);
    }
    
    fn main() {
        let _: Box<dyn Consumer> = todo!();
    }
    

    现在,错误稍微复杂了一点:

    error[E0038]: the trait `Consumer` cannot be made into an object
     --> src/main.rs:6:12
      |
    6 |     let _: Box<dyn Consumer> = todo!();
      |            ^^^^^^^^^^^^^^^^^ `Consumer` cannot be made into an object
      |
    note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
     --> src/main.rs:1:28
      |
    1 | trait Consumer where Self: Sized {
      |       --------             ^^^^^ ...because it requires `Self: Sized`
      |       |
      |       this trait cannot be made into an object...
    

    但是,有一个解决方法:没有必要限制整个特征 - 只是有问题的方法,正如我们从一开始就被告知的那样。移动where 子句,如下:

    trait Consumer {
        fn consume(self) where Self: Sized;
    }
    

    ...使上面的代码编译。

    现在,实际上使用这个特征对象怎么样?让我们实现它,例如,对于单元类型,并从main 使用它:

    trait Consumer {
        fn consume(self) where Self: Sized;
    }
    
    impl Consumer for () {
        fn consume(self) {}
    }
    
    fn main() {
        let consumer: Box<dyn Consumer> = Box::new(());
        consumer.consume();
    }
    

    另一个编译器错误!

    error: the `consume` method cannot be invoked on a trait object
      --> src/main.rs:11:14
       |
    2  |     fn consume(self) where Self: Sized;
       |                                  ----- this has a `Sized` requirement
    ...
    11 |     consumer.consume();
       |              ^^^^^^^
    

    同样,我们对方法的限制禁止了编译后没有意义的代码。

    【讨论】:

      猜你喜欢
      • 2022-10-05
      • 1970-01-01
      • 2022-12-07
      • 2016-05-29
      • 1970-01-01
      • 2021-12-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多