为什么在与“Self: Sized”绑定时不能调用特征对象上的函数?

Why can a function on a trait object not be called when bounded with `Self: Sized`?

我有以下代码:

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 绑定时它可以工作,但是我不能使用泛型来使函数对调用者来说更舒适。

这不是 which asks why you can't call baz from a trait object. I'm not asking why the bound is required; this has already been discussed 的副本。

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

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

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

那么为什么不能对特征对象执行此操作?

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

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

-编辑-

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

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

然而,Rust 试图明确编译器正在做什么,而这些隐式方法将违背这一点。无论如何,对于初学者来说,如果尝试在特征对象上调用泛型函数却无法编译,这不会让人感到困惑吗?

相反,Rust 让您显式 使整个特征不安全

trait Foo: Sized {

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

fn foo<T>() where Self: Sized {

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

Methods that take Self as an argument, return Self or otherwise require Self: Sized are not Object safe. That's because methods on a trait object are called via dynamic dispatch and the size of the trait implementation cannot be known at compile time. -- Peter Hall

引用 official docs:

Only traits that are object-safe can be made into trait objects. A trait is object-safe if both of these are true:

  • the trait does not require that Self: Sized
  • all of its methods are object-safe

So what makes a method object-safe? Each method must require that Self: Sized or all of the following:

  • must not have any type parameters
  • must not use Self

另请参阅:

  • from