Warning: file_get_contents(/data/phpspider/zhask/data//catemap/9/java/384.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Generics 为什么带有泛型类型参数对象的trait方法不安全?_Generics_Rust_Polymorphism_Traits_Trait Objects - Fatal编程技术网

Generics 为什么带有泛型类型参数对象的trait方法不安全?

Generics 为什么带有泛型类型参数对象的trait方法不安全?,generics,rust,polymorphism,traits,trait-objects,Generics,Rust,Polymorphism,Traits,Trait Objects,引用我的话 使用trait时用具体类型参数填充的泛型类型参数也是如此:具体类型成为实现trait的类型的一部分。当通过使用trait对象忘记了类型时,就无法知道要用什么类型填充泛型类型参数 我无法理解其理由。具体的例子,请考虑下面的 pub trait Echoer { fn echo<T>(&self, v: T) -> T; } pub struct Foo { } impl Echoer for Foo { fn echo<T>(&

引用我的话

使用trait时用具体类型参数填充的泛型类型参数也是如此:具体类型成为实现trait的类型的一部分。当通过使用trait对象忘记了类型时,就无法知道要用什么类型填充泛型类型参数

我无法理解其理由。具体的例子,请考虑下面的

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);
}
pub特征回音器{
fn-echo(&self,v:T)->T;
}
发布结构Foo{}
用于Foo的impl回声器{
fn回声(&self,v:T)->T{
println!(“v={}”,v);
返回v;
}
}
酒吧fn直通(e:Box,v:T){
返回e.echo(v);
}
fn main(){
设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.
$cargo run
编译gui v0.1.0(/tmp/gui)
错误[E0038]:无法将特征“回声器”制作成对象
-->src/main.rs:14:27
|
14 |酒吧fn通道(e:Box,v:T){
|^^^^^^^^^^^^^^^^^^^^^^`echoor`不能制作成对象
|
=帮助:考虑将“回声”移动到另一个特性
注意:为了使trait成为“对象安全的”,需要允许构建vtable以允许动态解析调用;有关更多信息,请访问
-->src/main.rs:2:8
|
1 |公共特征回音器{
|----这个特征不能被制成一个物体。。。
2 | fn回声(&self,v:T)->T;
|^^^^…因为方法'echo'具有泛型类型参数
错误:由于上一个错误而中止
有关此错误的详细信息,请尝试“rustc--explain E0038”。
错误:无法编译'gui'`
要了解更多信息,请使用--verbose再次运行该命令。
根据我的理解,即使
e
在被转换到trait对象时忘记了它的具体类型,它仍然可以推断它需要用
i32
填充
echo
的泛型类型参数,因为它在
passthrough
内部被调用,在编译时它是单态化为
passthrough

“具体类型成为实现trait的类型的一部分”是什么意思?为什么trait方法不能在编译时填充它们的泛型类型参数,例如只调用
echo

这与类似,但我将在这里详细说明

Rust-trait对象是使用

当Rust编译代码时,例如

pub fn passthrough<T>(e: Box<dyn Echoer>, v: T) {
    return e.echo(v);
}
pub-fn-passthrough(e:Box,v:T){
返回e.echo(v);
}
它需要决定调用什么
echo
函数。
Box
基本上是一个指向值的指针,对于您的代码来说,
Foo
将存储在堆上,
Box
将是指向
Foo
的指针。如果将其转换为
Box
,新的框实际上包含两个指针ers,一个到堆上的
Foo
,一个到a。这个vtable允许Rust在看到
e.echo(v)
时知道该做什么。您的
e.echo(v)的编译输出
call将查看vtable,找到
e
指向的任何类型的
echo
实现,然后调用它,在这种情况下,将
Foo
指针传递给
和self

对于一个简单的函数来说,这一部分很容易,但是这里的复杂性和问题是由于
fn echo(&self,v:T)的
部分造成的->T;
。模板函数本质上是为了使用单个定义声明多个函数,但如果需要vtable,它应该包含什么?如果您的trait包含一个具有类型参数(如
)的方法,其中可能需要未知数量的
T
类型。这意味着Rust需要不允许vtables引用具有类型参数的函数,否则它需要提前预测可能需要的所有可能的
T
类型,并将其包含在vtable中。Rust遵循第一个选项,并抛出与您看到的类似的编译器错误

在某些情况下,提前了解完整的
T
类型可能是可能的,对于在小代码库中工作的程序员来说,这似乎很清楚,但在任何非平凡的情况下,这将非常复杂,可能会产生非常大的vtables。除此之外,还需要Rust全面了解整个应用程序操作性编译。这可以极大地降低编译速度,至少是这样

例如,Rust通常独立于主代码编译依赖项,并且在编辑自己的项目代码时不需要重新编译依赖项。如果需要提前了解所有
T
类型以生成vtable,则需要处理所有依赖项和自己的所有代码,然后再决定使用哪种类型使用ch
T
值,然后才编译函数模板。类似地,假设依赖项包含代码,如问题中的示例所示,每次更改自己的项目时,Rust都必须检查您的更改是否使用以前未使用的类型参数对函数进行了动态调用,然后还需要重新编译依赖项,以便使用新引用的函数创建新的vtable


至少,它会带来更多的复杂性。

trait对象基本上是一个胖指针,包含两个指针,一个指向对象,另一个指向包含所有方法的vtable,因此从trait对象调用echo方法就像

trait\u object.vtable.echo(trait\u object.obj,“你好”)
假设echo可以是泛型的,那么在trait对象上构建vtable时,可能会有echo\u string、echo\u uint等,所有可能的类型都必须枚举。当分派方法时,它必须检查参数的类型,并从vtabl中找到实际的方法