In Rust, when a type is Copy
, should a method move (self
) or borrow (&self
) the type?
When a method from a non-Copy
type is called, there is a significant difference between a move (self
) and a borrow (&self
). For example, you would not be able to call a method that uses move (self
) twice. However, for a Copy
type, the difference between a move (self
) and a borrow (&self
) becomes less significant, at least for the caller.
In addition, as Rust will automatically dereference or borrow to look up a method, you can call a move (self
) method or a reference (&self
) method in the exact same manner.
I expect that there is little difference in each approach. When inlined by the Rust compiler, both appear to generate identical assembly code. At least from my experiment on the Godbolt compiler explorer. However, when not inlined the assembly code generated can be different. This is a trivial example, and therefore it should be taken with a grain of salt.
I couldn't establish a common practice from the standard library as there appears to be examples of both.
I don't think there should be much difference between each approach in the real world, although it does make me curious. Every time I implement Copy
on a type, I wonder what the best approach is...
I did find an example, whereby I must implement From
for both a value and a reference, otherwise the example will not compile. I am sure there are many more examples that exhibit similar behaviour, which may therefore make it more desirable to implment methods for either move (self
) or borrow (&self
).
What do you think is best? Does it matter? Is there a standard approach? Are there other cases that I should consider?
EDIT: At the moment I prefer to use a move (self
) for Copy
types.
My rule of thumb is:
If, when implementing on a non-Copy
type, this method will consume self
, do the same for Copy
types, because it is clearer and more uniform this way. An example of this is the arithmetic methods on Duration
, e.g. checked_add()
.
If not, then: for small types (when one machine bytes (up to usize) is definitely small, and two are usually small because they're passed in pair of registers, but three are already big because they're passed on stack) that are expected to stay small in future versions, take self
by value because it is faster this way; for other types (big or that may become big) take &self
, because taking self
will force a copy and will be (somewhat) slower.
An example of the first type is all methods on integers and floats and chars (well, almost all of them, some in char
take &self
for legacy reasons). An example of the second type is methods on Duration
or IpAddr
.
From
is a different story; it makes sense to implement it for both owned and references.