【问题标题】:Why can a function on a trait object not be called when bounded with `Self: Sized`?为什么不能调用 trait 对象上的函数时与 `Self: Sized` 绑定?
【发布时间】:2018-08-13 12:13:09
【问题描述】:

我有以下代码:

trait Bar {
    fn baz(&self, arg: impl AsRef<str>)
    where
        Self: Sized;
}

struct Foo;

impl Bar for Foo {
    fn baz(&self, arg: impl AsRef<str>) {}
}

fn main() {
    let boxed: Box<dyn Bar> = Box::new(Foo);
    boxed.baz();
}

playground

导致此错误的原因:

error: the `baz` method cannot be invoked on a trait object
  --> src/main.rs:15:11
   |
15 |     boxed.baz();
   |           ^^^

为什么这不可能?当我删除 Self: Sized 绑定时它可以工作,但是我不能使用泛型来使调用者更舒适。

这不是 Why does a generic method inside a trait require trait object to be sized? 的副本,它询问为什么不能从 trait 对象调用 baz。我不是在问为什么需要绑定;这有already been discussed

【问题讨论】:

    标签: rust traits trait-objects


    【解决方案1】:

    因为 Rust 的泛型系统通过单态化工作。

    例如,在 Java 中,泛型函数中的类型参数会转换为 Object 类型的变量,并根据需要进行强制转换。像这样的语言中的泛型只是作为一种工具来帮助验证代码中类型的正确性。

    Rust 和 C++ 等语言对泛型使用单态化。对于类型参数的每个组合,调用一个通用函数,生成专门的机器代码,该代码使用这些类型参数的组合运行该函数。该函数是单态的。这允许将数据存储在适当的位置,消除转换成本,并允许通用代码在该类型参数上调用“静态”函数。

    那么为什么你不能在 trait 对象上这样做呢?

    包括 Rust 在内的许多语言中的特征对象都是使用 vtable 实现的。当您有某种类型的指向 trait 对象的指针(原始、引用、Box、引用计数器等)时,它包含两个指针:指向数据的指针和指向 vtable 条目的指针 . vtable 条目是函数指针的集合,存储在不可变的内存区域中,指向该特征的方法的实现。因此,当您在 trait 对象上调用方法时,它会在 vtable 中查找实现的函数指针,然后间接跳转到该指针。

    不幸的是,Rust 编译器不能单态化函数,如果它在编译时不知道实现函数的代码,当你在 trait 对象上调用方法时就是这种情况。因此,您不能在 trait 对象上调用泛型函数(嗯,泛型而不是类型)。

    -编辑-

    听起来你在问为什么: Sized 限制是必要的。

    : Sized 使特征不能用作特征对象。我想可能有几个选择。 Rust 可以隐式地使任何具有泛型函数的 trait 都不是对象安全的。 Rust 还可以隐式地阻止在 trait 对象上调用泛型函数。

    然而,Rust 试图明确编译器正在做什么,而这些隐式方法将与之背道而驰。无论如何,对于初学者来说,尝试在 trait 对象上调用泛型函数并使其编译失败,这不会令人困惑吗?

    相反,Rust 允许您显式地使整个 trait 不是对象安全的

    trait Foo: Sized {

    显式地使某些功能仅可用于静态调度

    fn foo&lt;T&gt;() where Self: Sized {

    【讨论】:

    • 听起来你在问为什么: Sized 限制是必要的。:不:我不是在问,为什么需要限制。
    • 但是,我不知道: Sized 绑定会使特征不是对象安全。您基本上是在再次回答链接的问题。
    • "这个问题问的是,为什么你不能从 trait 对象调用 baz。"你不能从 trait 对象调用 baz 因为 baz 是一个泛型函数,你不能从 trait 对象调用泛型函数。
    • 那么“解决”这个问题的方法是什么,以便这个 Bar 特征变得可调用?
    • @Benoît 这是一个非常重要的问题,但在这个特定的示例中,您已经删除了 where Self: Sized 绑定,然后必须从方法中删除所有泛型。这里,泛型是impl AsRef&lt;str&gt;,这是一种秘密的类型参数。在这里,您可以简单地将其替换为 &amp;str
    【解决方案2】:

    绑定使方法不是对象安全的。非对象安全的特征不能用作类型。

    Self 作为参数、返回Self 或以其他方式要求Self: Sized 的方法不是对象安全的。这是因为 trait 对象上的方法是通过动态调度调用的,并且在编译时无法知道 trait 实现的大小。 -- Peter Hall

    引用official docs

    只有对象安全的特征才能成为特征对象。如果这两个都为真,则特征是对象安全的:

    • 该特征不需要Self: Sized
    • 它的所有方法都是对象安全的

    那么是什么让方法对象安全?每种方法都必须要求 Self: Sized 或以下所有条件:

    • 不能有任何类型参数
    • 不得使用Self

    另见:

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-12-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-01-10
      • 2020-05-09
      • 2022-06-29
      相关资源
      最近更新 更多