首页 > 解决方案 > &Trait 和 impl Trait 用作方法参数时有什么区别?

问题描述

到目前为止,在我的项目中,我使用许多特征来允许在单元测试中模拟/存根注入依赖项。然而,到目前为止我所做的一个细节似乎很可疑,以至于我很惊讶它甚至可以编译。我担心发生了一些我看不到或不理解的危险事情。它基于这两个方法签名之间的区别:

fn confirm<T>(subject: &MyTrait<T>) ...
fn confirm<T>(subject: impl MyTrait<T>) ...

我只是impl ...在方法参数中发现了语法,这似乎是唯一记录在案的方法,但是我的测试已经使用另一种方式通过了,这是我根据 Go 如何解决相同问题的直觉得出的(方法的大小编译时的参数,当参数可以是接口的任何实现者时,引用可以来拯救)。

这两者有什么区别?为什么他们都被允许?它们都代表合法的用例,还是我的参考语法(&MyTrait<T>)严格来说是一个更糟糕的主意?

标签: syntaxreferencerusttraits

解决方案


两者是不同的,服务于不同的目的。两者都很有用,根据情况,其中一个可能是最佳选择。

第一种情况,&MyTrait<T>最好用&dyn MyTrait<T>现代 Rust 编写。它是一个所谓的特征对象。引用指向任何实现的类型MyTrait<T>,并且方法调用在运行时动态分派。为了使这成为可能,引用实际上是一个胖指针;除了指向对象的指针外,它还存储指向对象类型的虚拟方法表的指针,以允许动态调度。如果您的对象的实际类型仅在运行时才知道,那么这是您可以使用的唯一版本,因为在这种情况下您需要使用动态调度。该方法的缺点是存在运行时成本,并且它仅适用于对象安全的特征。

第二种情况,impl MyTrait<T>,表示MyTrait<T>再次实现的任何类型,但在这种情况下,需要在编译时知道确切的类型。原型

fn confirm<T>(subject: impl MyTrait<T>);

相当于

fn confirm<M, T>(subject: M)
where
    M: MyTrait<T>;

对于代码中使用的每种类型M,编译器都会confim在二进制文件中创建一个单独的版本,并且方法调用在编译时静态分派。如果在编译时所有类型都已知,则此版本更可取,因为您无需支付动态分派到具体类型的运行时成本。

两个原型之间的另一个区别是第一个版本subject通过引用接受,而第二个版本使用传入的参数。但这不是概念上的区别——虽然第一个版本不能被编写为使用对象,但第二个版本可以很容易地写成subject通过引用接受:

fn confirm<T>(subject: &impl MyTrait<T>);

鉴于您引入了特性以方便测试,您可能应该更喜欢&impl MyTrait<T>.


推荐阅读