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

Tim*_*ann 5 traits rust trait-objects

我有以下代码:

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();
}
Run Code Online (Sandbox Code Playgroud)

操场

这导致此错误:

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

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

这不是为什么 trait 中的泛型方法需要调整 trait 对象的大小?这问为什么你不能baz从特征对象调用。我不是问为什么需要绑定;这已经讨论过了

Pho*_*nix 5

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

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

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

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

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

不幸的是,如果 Rust 编译器在编译时不知道实现该函数的代码,则 Rust 编译器不能单态化函数,这就是在 trait 对象上调用方法时的情况。出于这个原因,您不能在 trait 对象上调用泛型函数(好吧,泛型覆盖类型)。

-编辑-

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

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

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

相反,Rust 让你明确地使整个 trait 不是对象安全的

trait Foo: Sized {

或者明确地使某些功能仅可用于静态调度

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