当用作方法参数时,&Trait和impl Trait有什么区别?

时间:2018-12-13 19:54:48

标签: syntax reference rust traits

到目前为止,在我的项目中,我使用许多特性来允许在单元测试中进行模拟/存根以注入注入的依赖项。但是,到目前为止,我正在做的一个细节似乎太可疑了,甚至令我惊讶。我担心发生了一些看不见或不了解的危险。它基于这两个方法签名之间的区别:

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

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

两者之间有什么区别?为什么都允许他们呢?它们都代表合法的用例,还是我的引用语法(&MyTrait<T>)严格来说是个更差的主意?

2 个答案:

答案 0 :(得分:6)

两者是不同的,并且用途不同。两者都是有用的,并且视情况而定,一个或另一个可能是最佳选择。

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

第二种情况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>

答案 1 :(得分:1)

确实不一样。 revision, err := srv.Revisions.Get(fileId, revisionId).Do() //fieldId and revisionId are fatched using proper calls if err != nil { log.Fatalf("Unable to retrieve revision: %v", err) } fmt.Println("Revision:") fmt.Printf("%+v\n", revision.LastModifyingUser) 版本等效于以下内容:

impl

因此,与第一个版本不同,fn confirm<T, M: MyTrait<T>>(subject: M) ... 被移动(通过值传递)到subject中,而不是通过引用传递。因此,在confirm版本中,impl拥有此值的所有权。