I am implementing math operations on it, and at first I was doing what was obvious at first and implementing everything with references:

impl ops::Mul<Self> for &Chance {
type Output = Chance;
fn mul(self, rhs: Self) -> Self::Output { ... }
}

However, when implementing MulAssign I realized that since the type is Copy, I could just implement it without references, and mutate self, which is automatically copied when calling, so the same amount of allocation is happening regardless. It also means I could just call my implementation of MulAssign from Mul.

So my questions are:

Is this in general frowned upon?

Is my understanding of the interaction of taking mut self on a Copy type wrong in some way?

Is there something else that I'm blatantly missing?

"Two machine words/pointer-sized values" is just about the exact point at which the value is small enough that passing it by move is definitely more efficient.^{[1]} Therefore, if you have only one set of operator implementations, it should be the one that takes values and not references. But providing both versions (as the standard library number types do) provides convenience.

If it were any larger, then the compiler would be passing it by pointer implicitly regardless. âŠī¸

That makes sense as far as the optimization of the implementation goes, but I'm mainly focused on whether implementing the non-mutative operations via implicit copy and mutation has any hidden consequences I'm not thinking of. Also just the general question if this is normal for an implementation detail or in general people would find this code "surprising" and avoid it for more straightforward immutable creation of a return object.

You can also take the rhs argument by value for symmetry:

impl ops::Mul<Self> for Chance

You can also go the other way: implement MulAssign in terms of Mul. For multiplication either approach is equally clear, but for addition it seems more natural that way: