syntax - &Trait 和 impl Trait 用作方法参数时有什么区别?
问题描述
到目前为止,在我的项目中,我使用许多特征来允许在单元测试中模拟/存根注入依赖项。然而,到目前为止我所做的一个细节似乎很可疑,以至于我很惊讶它甚至可以编译。我担心发生了一些我看不到或不理解的危险事情。它基于这两个方法签名之间的区别:
fn confirm<T>(subject: &MyTrait<T>) ...
fn confirm<T>(subject: impl MyTrait<T>) ...
我只是impl ...
在方法参数中发现了语法,这似乎是唯一记录在案的方法,但是我的测试已经使用另一种方式通过了,这是我根据 Go 如何解决相同问题的直觉得出的(方法的大小编译时的参数,当参数可以是接口的任何实现者时,引用可以来拯救)。
这两者有什么区别?为什么他们都被允许?它们都代表合法的用例,还是我的参考语法(&MyTrait<T>
)严格来说是一个更糟糕的主意?
解决方案
两者是不同的,服务于不同的目的。两者都很有用,根据情况,其中一个可能是最佳选择。
第一种情况,&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>
.
推荐阅读
- c - C - Pthreads线程进入无限循环,除非我在两者之间打印输出
- node.js - 猫鼬不会填充参考
- flutter - 异常的 dartfmt 行为。怎么了?
- mysql - 在分组子句中选择最大计数
- r - 在R中的数据框中计算分隔的唯一字符串
- javascript - 如何使用 amCharts v4 将项目符号和工具提示定位在阶梯线图的中心?
- javascript - 为什么我在使用 createRef() 时在 React 中收到意外的令牌错误
- r - 什么功能按类别将表分成多个表?
- python - 是否有任何 python API 来收集 kubernetes VPA 数据
- css - 材质ui分页组件文本颜色为灰色