【问题标题】:Differences generic trait-bounded method vs 'direct' trait method通用特征有界方法与“直接”特征方法的区别
【发布时间】:2019-11-21 16:22:30
【问题描述】:

我有这个代码:

fn main() {
    let p = Person;
    let r = &p as &dyn Eatable;

    Consumer::consume(r);
    // Compile error
    Consumer::consume_generic(r);
}

trait Eatable {}

struct Person;

impl Eatable for Person {}

struct Consumer;

impl Consumer {
    fn consume(eatable: &dyn Eatable) {}
    fn consume_generic<T: Eatable>(eatable: &T) {}
}

错误:

dyn Eatable 类型值的大小无法在 编译时间

我觉得很奇怪。我有一个方法,它实际上采用dyn Eatable 并编译得很好,所以该方法以某种方式知道Eatable 的大小。泛型方法 (consume_generic) 将针对每种使用的类型正确编译以提高性能,而 consume 方法则不会。

所以出现了几个问题:为什么编译器出错? 方法体内是否有一些东西可以让我做其他方法无法做到的事情?我应该在什么时候更喜欢其中一个?

旁注:我也针对 Swift 语言提出了这个问题:Differences generic protocol type parameter vs direct protocol type。在 Swift 中,我得到相同的编译错误,但底层错误不同:协议/特征不符合自身(因为 Swift 协议可以保存初始化程序、静态事物等,这使得一般地引用它们变得更加困难)。我也在Java中尝试过,我相信泛型类型被删除了,它完全没有区别。

【问题讨论】:

    标签: rust


    【解决方案1】:

    问题不在于函数本身,而在于类型的 trait bound。

    Rust 中的每个泛型类型都有一个隐含的Sized 界限:因为这在大多数情况下是正确的,所以决定不强制开发人员每次都写出来。但是,如果您仅在某种引用后面使用此类型,就像您在此处所做的那样,您可能希望通过指定 T: ?Sized 来解除此限制。如果你添加这个,你的代码会编译得很好:

    impl Consumer {
        fn consume(eatable: &dyn Eatable) {}
        fn consume_generic<T: Eatable + ?Sized>(eatable: &T) {}
    }
    

    Playground as a proof


    至于其他问题,主要区别在于静态调度与动态调度。

    当您使用泛型函数(或语义上等效的impl Trait 语法)时,函数调用是静态分派的。也就是说,对于传递给函数的每种类型的参数,编译器都会独立于其他参数生成定义。在大多数情况下,这可能会产生更优化的代码,但缺点可能是更大的二进制大小和 API 中的一些限制(例如,您不能通过这种方式轻松创建异构集合)。

    当您使用dyn Trait 语法时,您选择了动态调度。必要的数据将存储到附加到 trait 对象的表中,并且将在运行时为每个 trait 方法选择正确的实现。然而,消费者只需要编译一次。这通常会比较慢,这既是由于间接性的原因,也是因为单个优化是不可能的,但更灵活。


    至于建议(请注意,这是一种观点,而不是事实) - 我会说最好尽可能坚持泛型,并且只有在无法实现目标时才将其更改为 trait 对象。

    【讨论】:

    • 好的,编译好了:)。但主要问题是:这两种方法有什么区别?您什么时候更喜欢其中之一?
    • 已编辑,希望这是您要求的内容。
    猜你喜欢
    • 1970-01-01
    • 2020-04-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-01-10
    • 2011-06-03
    • 2013-12-21
    相关资源
    最近更新 更多