Ergonomics of wrapping operations


#41

aha, I see that now. Thanks for the clarification :smile: consider this a friendly bump for the topic then :slight_smile:


#42

Yeah, the current recommendation is to either use wrapping_xxx methods or the Wrapping<T> types, whichever is more convenient.

I am curious to find out why all of your code would need to switch to Wrapping. I’d have thought that you’d only need to deal with Wrapping at the interface boundary?


#43

FWIW, one of the major pain-points with Wrapping<T> is that heterogeneous operations - e.g. T + Wrapping<T> - are not permitted. This is because it’s not obvious whether such an operation should wrap or not. In different words, one of the operands essentially needs to be casted before performing the operation, and it’s not clear which one.

I’m now kind of leaning towards saying that Wrapping<T> should “win”: in other words, that we should have impl Add<u32> for Wrapping<u32> { type Output = Wrapping<u32>; ... } (and vice versa), and likewise for the other types. The intuition is this: the reason why “plain” T (e.g. u32) panics on over/underflow is not because this is well-known to be the behavior that’s desired by users of u32; rather, the reason is precisely that we don’t have any information about the programmer’s intent, so our only reasonable option is to signal the unhandled circumstance by panicking. (This is also underscored by the fact that panicking only happens in debug builds: if we knew that panicking is what the programmer explicitly wants, then we would do it always.)

Now in the case of u32 + Wrapping<u32>, if we think of it as a unification problem, then “no available information” unifies with “it should wrap” to yield “it should wrap”.

(Incidentally, for completeness’s sake, we should presumably also have Saturating<T>, Checked<T>, and Overflowing<T> for the other possible policies, with analogous impls. And see also.)


#44

I disagree. This would amount to making one Wrapping value “infect” all surrounding operations with non-wrapping values; and sometimes wrapping can lead to counter-intuitive results. Therefore if we allow it, the Output type should be u32. One can always add another Wrapping(…) around it to ensure the result wraps.


#45

Hmm. The idea was actually to protect against accidents by giving a type error if u32 was expected but a Wrapping<u32> resulted (i.e., the scenario where I wrote T + Wrapping<T> but didn’t actually want it to wrap). I’m not sure how that squares with the “unification of information” idea, actually. I can see how it might backfire when the result type is inferred instead of checked.

One place where there aren’t questions around the choice of result type, though, is AddAssign (+=) and family. I think it would be completely, unambiguously fine to have impl AddAssign<u32> for Wrapping<u32> as a wrapping operation. (Right now it seems we don’t even have AddAssign<Wrapping<u32>> for Wrapping<u32>!)


#46

I worry that this could get rather confusing for more complicated expressions. For example, even just u32 + u32 + Wrapping<u32> would yield a Wrapping<u32>, but could still panic if the first addition overflowed. Maybe that’s what is desired, but it seems more likely that it isn’t.