![](/img/trans.png)
[英]Why does a generic method inside a trait require trait object to be 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();
}
这导致此错误:
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
。 我不是问为什么需要绑定; 这已经讨论过了。
因为 Rust 的泛型系统是通过单态来工作的。
例如,在 Java 中,泛型函数中的类型参数变成 Object 类型的变量,并根据需要进行强制转换。 像这样的语言中的泛型只是作为一种工具来帮助验证代码中类型的正确性。
Rust 和 C++ 等语言对泛型使用单态化。 对于调用泛型函数的每个类型参数组合,生成专门的机器代码,使用这些类型参数组合运行该函数。 函数是单态的。 这允许将数据存储到位,消除转换成本,并允许泛型代码在该类型参数上调用“静态”函数。
那么为什么不能在 trait 对象上这样做呢?
许多语言中的 Trait 对象,包括 Rust,都是使用vtable实现的。 当您有某种类型的指向 trait 对象(原始、引用、Box、引用计数器等)的指针时,它包含两个指针:指向数据的指针和指向vtable 条目的指针。 vtable 条目是函数指针的集合,存储在不可变的内存区域中,指向该 trait 方法的实现。 因此,当您在 trait 对象上调用一个方法时,它会在 vtable 中查找实现的函数指针,然后间接跳转到该指针。
不幸的是,如果 Rust 编译器在编译时不知道实现该函数的代码,则 Rust 编译器不能单态化函数,当您在 trait 对象上调用方法时就是这种情况。 出于这个原因,您不能在 trait 对象上调用泛型函数(好吧,泛型覆盖类型)。
-编辑-
听起来你在问为什么: Sized
限制是必要的。
: Sized
使得 trait 不能用作 trait 对象。 我想可能有几种选择。 Rust 可以隐式地使具有泛型函数的任何特征都不是对象安全的。 Rust 还可以隐式地阻止在 trait 对象上调用泛型函数。
然而,Rust 试图明确编译器正在做什么,而这些隐式方法会违背这一点。 无论如何,对于初学者来说,尝试在 trait 对象上调用泛型函数并且编译失败,这会不会让人感到困惑?
相反,Rust 让你明确地使整个 trait 不是对象安全的
trait Foo: Sized {
或者明确地使某些功能仅可用于静态调度
fn foo<T>() where Self: Sized {
绑定使方法不是对象安全的。 非对象安全的特征不能用作类型。
将
Self
作为参数、返回Self
或以其他方式需要Self: Sized
方法不是对象安全的。 这是因为 trait 对象上的方法是通过动态调度调用的,并且在编译时无法知道 trait 实现的大小。 —— 彼得·霍尔
引用官方文档:
只有对象安全的 trait 才能被做成 trait 对象。 如果这两个都为真,则 trait 是对象安全的:
- 该特征不需要
Self: Sized
- 它的所有方法都是对象安全的
那么是什么使方法对象安全呢? 每个方法都必须要求
Self: Sized
或以下所有内容:
- 不能有任何类型参数
- 不得使用
Self
也可以看看:
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.