A few weeks ago, an event I like to refer to as the wrapocalypse occurred: when Rust started panic!()ing on wrapping math. This event may not have been noticed by many in the Rust community, but, as the maintainer of Rust-Crypto, I, unfortunately, was quite aware of it (ideally, I would have dealt with it earlier, but, c’est la vie). Anyway, the result was that Rust-Crypto wouldn’t work anymore in debug mode due to the large number of operations in Rust-Crypto that required wrapping math.
So, I set out to fix it. Attempt #1 was to wrap all that variables that needed wrapping behavior in the Wrapping newtype. As others have pointed out, Wrapping has a variety of ergonomic issues. After struggling with that for a while, I gave up on that approach. Instead, I converted all of the operations that needed wrapping behavior to user wrapping_{add,sub,mul} which solved the problem.
Based on that experience, my strong suggestions is to get rid of Wrapping altogether. It has all the ergonomic issues mentioned above. Worse, its highly un-ergonomic to transform a u32 into a Wrapping<u32>, do some math, and then transforms it back out again. In fact, that’s the primary reason that I gave up on it. Any variable that needs wrapping math needs to come from somewhere before you operate on it. Once you’re done with whatever calculations you need to do, it needs to go somewhere. The problem is that whatever function produced it (for example: a functions reading a vector of u32s out of a [u8]) probably wants to return a [u32]. And whatever function you want to pass it to next (maybe a function writing it to another [u8]), probably wants a u32 as well. Having to new-type the value before working with it meant that there was a tremendous amount of boilerplate code just wrapping and unwrapping numbers. The wrapping_{add,sub,mul} functions don’t have this problem, and, at least in the case of Rust-Crypto, are much easier to use.
I think the basic problem with Wrapping is, as others have suggested, that wrapping arithmetic is a type of operation on a number. Its a property of the operation as opposed to a property of the type. Right now, when you put a (key, value) into a HashMap, it the key already exists in the map, then the existing value is replaced with the new value. As an alternative, we could reject the update. If we wanted to provide such a method, I think the way we’d do it would be via a new method that had those semantics. I don’t think we’d create a newtype wrapper for HashMaps that modifies the behavior of the put operation - its un-ergonomic and it hides what the operation does at the point where it used based on the declaration of the type which might not be anywhere near the use.
The suggestion for `#[wrapping] might make sense for annotating a bit of code that is performance critical. But, for the more general case of trying to do wrapping math, I think its awkward. Just because I want 51% of the math operations in a function to be wrapping, doesn’t mean that I want the remaining 49% to have that behavior as well. Its also a kind of “spooky action at a distance” - I can’t just look at the operation being performed to see whats happening, I also have to look at the function declaration. Its basically declaring a new subset of Rust that some functions can opt into but others won’t. It may solve some ergonomic issues, but, I think it will introduce much bigger, more profound ones.
So, I described it as the “wrapocalypse” - but, the end result of all that work is that now Rust-Crypto is much better - if you read through the code, it is explicitly clear which operations wrap and which don’t. This is a huge win. But, I think there is too much focus on “how can we make newtypes work” - I don’t think they can. wrapping_{add,sub,mul} is not especially ergonomic, but, even in a code base that makes unusually high use of wrapping operations, I’d say that the transition to these operations was a big win. I think the solution to the ergonomic problem is to treat the wrapping ops as distinct ops - give them their own operations ala Swift. It fixes all of the ergonomic issues (in my opinion) while keeping the code clear and not inflicting wrapping math on operations that shouldn’t be wrapping just because they happen to be located in a function that is doing lots of wrapping math.