gpt4 book ai didi

generics - 为什么具有泛型类型参数的 trait 方法是对象不安全的?

转载 作者:行者123 更新时间:2023-12-04 13:08:51 27 4
gpt4 key购买 nike

报价 the Book (强调我的),

The same is true of generic type parameters that are filled in with concrete type parameters when the trait is used: the concrete types become part of the type that implements the trait. When the type is forgotten through the use of a trait object, there is no way to know what types to fill in the generic type parameters with.


我无法理解其中的原理。对于一个具体的例子,考虑以下
pub trait Echoer {
fn echo<T>(&self, v: T) -> T;
}

pub struct Foo { }

impl Echoer for Foo {
fn echo<T>(&self, v: T) -> T {
println!("v = {}", v);
return v;
}
}

pub fn passthrough<T>(e: Box<dyn Echoer>, v: T) {
return e.echo(v);
}

fn main() {
let foo = Foo { };
passthrough(foo, 42);
}
结果当然是报错
$ cargo run
Compiling gui v0.1.0 (/tmp/gui)
error[E0038]: the trait `Echoer` cannot be made into an object
--> src/main.rs:14:27
|
14 | pub fn passthrough<T>(e: Box<dyn Echoer>, v: T) {
| ^^^^^^^^^^^^^^^ `Echoer` cannot be made into an object
|
= help: consider moving `echo` to another trait
note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
--> src/main.rs:2:8
|
1 | pub trait Echoer {
| ------ this trait cannot be made into an object...
2 | fn echo<T>(&self, v: T) -> T;
| ^^^^ ...because method `echo` has generic type parameters

error: aborting due to previous error

For more information about this error, try `rustc --explain E0038`.
error: could not compile `gui`

To learn more, run the command again with --verbose.
据我了解,即使 e抛入 trait 对象时忘记了它的具体类型,仍然可以推断它需要填充 echo<T> 的泛型类型参数。与 i32 , 因为它是在内部调用的 passthrough<T> ,它被单晶化为 passthrough<i32>在编译时。
“具体类型成为实现特征的类型的一部分”是什么意思?为什么 trait 方法不能在编译时填充它们的泛型类型参数,例如只需拨打 echo<i32> ?

最佳答案

这类似于 Why does a generic method inside a trait require trait object to be sized?但我会在这里详细说明。
Rust 特征对象是 fat pointers使用 vtable 实现.
当 Rust 编译诸如

pub fn passthrough<T>(e: Box<dyn Echoer>, v: T) {
return e.echo(v);
}
它需要决定什么 echo调用的函数。一个 Box基本上是一个指向值的指针,就您的代码而言,是 Foo将存储在堆上,并且 Box<Foo>将是指向 Foo 的指针.如果您随后将其转换为 Box<dyn Echoer> ,新的 Box 实际上包含两个指针,一个指向 Foo在堆上,一个到一个 vtable .这个 vtable 允许 Rust 在看到 e.echo(v) 时知道该做什么。 .您的 e.echo(v) 的编译输出调用将查看 vtable 以找到 echo任何类型的实现 e指向,然后调用它,在这种情况下传递 Foo &self 的指针.
在函数简单的情况下,这部分很容易,但由于 <T>,这里的复杂性和问题也随之而来。 fn echo<T>(&self, v: T) -> T; 的一部分.模板函数本质上旨在使用单个定义声明许多函数,但是如果需要一个 vtable,它应该包含什么?如果您的特征包含一个具有类型参数的方法,例如 <T> ,其中是未知数 T可能需要的类型。这意味着 Rust 需要要么禁止引用带有类型参数的函数的 vtables,要么它需要提前预测每个可能的 T可能需要的类型,并将其包含在 vtable 中。 Rust 遵循第一个选项并抛出您所看到的编译器错误。
虽然知道全套 T在某些情况下,提前类型可能是可能的,并且对于在小型代码库中工作的程序员来说似乎很清楚,这将非常复杂,并且在任何非平凡的情况下可能会产生非常大的 vtable。它还需要 Rust 全面了解您的整个应用程序,以便正确编译。这至少会大大减慢编译时间。
例如,Rust 通常将依赖项与您的主代码分开编译,并且在您编辑自己的项目代码时不需要重新编译您的依赖项。如果您需要了解所有 T提前types生成vtable,需要先处理好所有依赖和自己的所有代码,然后再决定用哪个 T值被使用,然后才编译函数模板。类似地,假设依赖项包含与您问题中的示例类似的代码,每次您更改自己的项目时,Rust 都必须检查您的更改是否引入了对具有以前未使用过的类型参数的函数的动态调用,然后它还需要重新编译依赖项,以便使用新引用的函数创建一个新的 vtable。
至少,它会引入大量额外的复杂性。

关于generics - 为什么具有泛型类型参数的 trait 方法是对象不安全的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/67767207/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com