6

我有以下代码:

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();
}

操场

导致此错误:

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

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

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

4

2 回答 2

6

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

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

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

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

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

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

-编辑-

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

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

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

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

trait Foo: Sized {

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

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

于 2018-08-14T19:14:09.827 回答
1

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

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

引用官方文档

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

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

那么是什么让方法对象安全呢?每种方法都必须Self: Sized满足以下条件或全部条件:

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

也可以看看:

于 2018-08-14T21:38:40.153 回答