当使用`Self: Sized`限定时,为什么无法调用特质对象上的函数?

8

我有以下代码:

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限制时,它可以工作,但是这样就无法使用泛型,而泛型使函数对调用者更加舒适。
这不是为什么需要trait对象大小的通用方法需要trait对象大小?的重复问题,该问题询问为什么不能从trait对象调用baz。我不是在问为什么需要该限制;这已经被讨论过了
2个回答

8

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

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

Rust和C++等语言使用单态化来实现泛型。对于每个类型参数组合,都会生成专门的机器码来运行该函数。该函数被单态化。这样可以在原地存储数据,消除了类型转换的成本,并允许泛型代码调用该类型参数上的“静态”函数。

那么为什么不能在trait对象上做同样的操作呢?

许多编程语言(包括 Rust)中的 Trait 对象是使用 vtable 实现的。当您拥有某种类型的指向 Trait 对象的指针(原始指针、引用、Box、引用计数等),它包含两个指针:数据指针和指向 vtable 条目的指针。vtable 条目是一个函数指针集合,存储在不可变的内存区域中,它们指向该 Trait 方法的实现。因此,当您在 Trait 对象上调用方法时,它会在 vtable 中查找实现的函数指针,然后进行间接跳转到该指针。
不幸的是,如果 Rust 编译器不知道实现函数的代码(在调用 Trait 对象上的方法时就是这种情况),则无法单态化函数。因此,您不能在 Trait 对象上调用泛型函数(即对类型进行泛型)。
-编辑-
看起来你正在问为什么需要 Sized 限制。
Sized 使得该 Trait 不能被用作 Trait 对象。我想也可能有一些替代方案。Rust 可以隐式地使任何具有泛型函数的 Trait 不对象安全。Rust 也可以隐式地防止在 Trait 对象上调用泛型函数。

然而,Rust试图明确编译器正在做什么,这些隐式方法会违反这一点。对于初学者来说,尝试在trait对象上调用通用函数并使其无法编译是否会令人困惑呢?

相反,Rust允许您显式地使整个trait不适用于对象

trait Foo: Sized {

或者显式地使某些函数仅在静态分派时可用

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


听起来你是在问为什么需要 : Sized 限制。不,我并没有问为什么需要这个限制。 - Tim Diekmann
然而,我没有意识到 : Sized 约束会使 trait 不具备对象安全性。你基本上是再次回答了链接的问题。 - Tim Diekmann
这个问题是关于为什么你不能从trait对象调用baz的原因。你不能从trait对象调用baz,因为baz是一个泛型函数,你不能从trait对象调用泛型函数。 - Phoenix
那么,解决这个问题的方法是什么,使得这个 Bar 特质可以被调用? - Benoît
1
@Benoît 这是一个非常不平凡的问题,但在这个具体的例子中,您可以删除 where Self: Sized 的限制,然后必须从该方法中删除所有泛型。在这里,泛型是 impl AsRef<str>,它有点像一个秘密的类型参数。在这里,您可以简单地将其替换为 &str - Phoenix

1

该限制使得该方法不具备对象安全性。不具备对象安全性的特质不能用作类型。

Self作为参数、返回Self或者要求Self: Sized的方法都不具备对象安全性,因为特质对象上的方法通过动态分发调用,特质实现的大小在编译时无法确定。-- Peter Hall

引用官方文档:

只有符合对象安全的特性才能被制作成特性对象。如果一个特性同时满足以下两个条件,那么它就是对象安全的:该特性不要求 Self: Sized,并且其所有方法都是对象安全的。什么使得方法对象安全呢?每个方法必须要求 Self: Sized 或者满足以下所有条件:没有任何类型参数,不使用 Self

另请参阅:


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接