What is the difference between &Trait and impl Trait when used as method arguments?

后端 未结 2 1149
隐瞒了意图╮
隐瞒了意图╮ 2020-12-11 03:25

In my project so far, I use many traits to permit mocking/stubbing in unit tests for injected dependencies. However, one detail of what I\'m doing so far seems so suspicious

相关标签:
2条回答
  • 2020-12-11 03:36

    It is indeed different. The impl version is equivalent to the following:

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

    so unlike the first version, subject is moved (passed by value) into confirm, rather than passed by reference. So in the impl version, confirm takes ownership of this value.

    0 讨论(0)
  • 2020-12-11 03:58

    The two are different, and serve different purposes. Both are useful, and depending on circumstances one or the other may be the best choice.

    The first case, &MyTrait<T>, is preferably written &dyn MyTrait<T> in modern Rust. It is a so-called trait object. The reference points to any type implementing MyTrait<T>, and method calls are dispatched dynamically at runtime. To make this possible, the reference is actually a fat pointer; apart from a pointer to the object it also stores a pointer to the virtual method table of the type of the object, to allow dynamic dispatch. If the actual type of your object only becomes known at runtime, this is the only version you can use, since you need to use dynamic dispatch in that case. The downside of the approach is that there is a runtime cost, and that it only works for traits that are object-safe.

    The second case, impl MyTrait<T>, denotes any type implementing MyTrait<T> again, but in this case the exact type needs to be known at compile time. The prototype

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

    is equivalent to

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

    For each type M that is used in your code, the compiler creates a separate version of confim in the binary, and method calls are dispatched statically at compile time. This version is preferable if all types are known at compile time, since you don't need to pay the runtime cost of dynamically dispatching to the concrete types.

    Another difference between the two prototypes is that the first version accepts subject by reference, while the second version consumes the argument that is passed in. This isn't a conceptual difference, though – while the first version cannot be written to consume the object, the second version can easily be written to accept subject by reference:

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

    Given that you introduced the traits to facilitate testing, it is likely that you should prefer &impl MyTrait<T>.

    0 讨论(0)
提交回复
热议问题