Rust 如何使用包含返回对Self的引用的方法的trait对象?
使用包含返回对Rust 如何使用包含返回对Self的引用的方法的trait对象?,rust,Rust,使用包含返回对Self的引用的方法的trait对象的正确方法是什么?下面的代码 trait Foo{ fn gen(&mut self)->&self; fn评估(自我)->f64; } 结构A{ a:f64, } 求一个{ fn gen(&mut self)->&self{ 自我评价a=1.2; 自己 } fn评估(自我)->f64{ 自我评价a+2.3 } } 结构B; 对B的建议{ fn gen(&mut self)->&self{ 自己 } fn评估(自我)->f64{ 3.4 } }
Self
的引用的方法的trait对象的正确方法是什么?下面的代码
trait Foo{
fn gen(&mut self)->&self;
fn评估(自我)->f64;
}
结构A{
a:f64,
}
求一个{
fn gen(&mut self)->&self{
自我评价a=1.2;
自己
}
fn评估(自我)->f64{
自我评价a+2.3
}
}
结构B;
对B的建议{
fn gen(&mut self)->&self{
自己
}
fn评估(自我)->f64{
3.4
}
}
fn酒吧(f:&dyn Foo){
println!(“结果是:{}”,f.eval());
}
fn main(){
设mut-aa=A{A:0};
bar(aa.gen());
设mut-bb=B;
bar(bb.gen());
}
给出编译器错误
error[E0038]:无法将特征'Foo'生成对象
-->src/main.rs:30:1
|
3 | fn gen(&mut self)->&self;
|---方法“gen”在其参数或返回类型中引用“Self”类型
...
30 | fn酒吧(f:&dyn Foo){
|^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
现在,我们至少可以用两种方法中的一种来解决这个问题。我们可以将gen
的定义修改为:
trait Foo{
fn gen(和mut self)->和self,其中self:Sized;
fn评估(自我)->f64;
}
或者,我们可以将bar的定义修改为:
fn条(f:&f),其中f:Foo+?大小{
println!(“结果是:{}”,f.eval());
}
也就是说,我不明白两者之间的区别,也不明白应该在什么情况下使用,或者是否应该使用另一种方法。这里的关键是理解错误本身的原因。使用您的函数
fn bar(f : &dyn Foo) {
您可以调用f.gen()
(给定Foo的当前定义),但这不受支持,因为我们不知道它将返回什么类型!在特定代码的上下文中,它可以是A
或B
,在一般情况下,任何东西都可以实现该特性。这就是为什么
无法将traitFoo
制成对象
如果它可以被做成一个trait对象,那么试图使用该对象的引用的代码就不会得到很好的定义,比如f.gen()
现在,我们至少可以用两种方法中的一种来解决这个问题。我不明白这两种方法之间的区别,也不知道应该在什么情况下使用,或者是否应该使用另一种方法
fn gen(&mut self)->&self,其中self:size;
这个函数,因为它现在对Self
有一个限制,实际上不能被bar
函数使用,因为dyn Foo
没有size
。如果你把这个限制放在适当的位置并尝试在bar
内部调用f.gen()
无法对trait对象调用gen
方法
fn条(f:&f),其中f:Foo+?大小{
这种方法解决了这个问题,因为我们确实知道f.gen()
将返回什么类型(f
)。还要注意,这可以简化为fn-bar(f:&f){
,甚至fn-bar(f:&impl-Foo){
除非您真的在性能方面进行了超级优化,至少在某种程度上这是您的首选。您是希望传递一个trait对象,还是需要对对象传递到的每个函数执行
更技术性的回答:
在技术方面,您可能不需要担心,这里的权衡是性能与可执行代码大小
由于函数内部明确知道类型F
,因此您的通用bar
函数实际上会在编译的输出可执行文件中创建bar
函数的多个副本,就像您执行fn bar_A(F:&A){
和fn bar_B(F:&B)一样{
。此过程称为monomorphization
这个过程的好处是,因为函数有独立的副本,编译器可以更好地优化函数的代码,调用函数的位置也可以,因为F
的类型是提前知道的
,bar\u A
将始终调用A::eval
,bar\u B
将始终调用B::eval
,当您调用bar(aa.gen());
时,它已经知道它正在调用bar\u A(aa.gen())
这里的缺点是,如果您有许多实现了Foo
的类型,并且您为所有这些类型调用bar
,那么您将为这些类型创建同样多的bar\uxxx
副本。这将使最终的可执行文件更大,但可能更快,因为编译器可以优化所有已知的类型把东西放在一起
另一方面,如果使用fn-bar(f:&dyn-Foo){
,这两点可能会发生翻转。由于可执行文件中只有一个bar
副本,因此在调用f.eval()时,它不知道f
引用的类型
,这意味着您错过了潜在的编译器优化,您的函数需要这样做。f:&f
知道类型f
,f:&dyn Foo
需要查看与f
相关的元数据,以确定要调用哪个trait实现的eval
这一切都意味着对于f:&dyn Foo
,您的最终可执行文件将更小,这可能有利于RAM的使用,但如果将bar
作为应用程序核心逻辑循环的一部分调用,那么它可能会更慢
有关更多解释,请参阅