Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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 that I'm surprised it even compiles. I'm worried that something dangerous is going on that I don't see or understand. It's based on the difference between these two method signatures:

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

I only just discovered the impl ... syntax in method arguments, and it seems like the only documented way to do this, but my tests pass using the other way already, which I came to by intuition based on how Go solves the same problem (size of method argument at compile time, when argument can be any implementer of an interface, and references can come to the rescue).

What is the difference between these two? And why are they both allowed? Do they both represent legitimate use cases, or is my reference syntax (&MyTrait<T>) strictly a worse idea?

like image 895
qqq Avatar asked Dec 13 '18 19:12

qqq


2 Answers

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>.

like image 166
Sven Marnach Avatar answered Oct 17 '22 06:10

Sven Marnach


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.

like image 26
Tarmil Avatar answered Oct 17 '22 06:10

Tarmil