Comment by uecker

8 hours ago

I am not convinced. A byte is for low-level accessing of memory, you shouldn't really do any computation with it, except maybe low-level bit-fiddling or crypto, but then the non-wrapping non-negative inter is not correct either.

Natural numbers are nice, but then we invented zero and negative number so we got a group structure for addition which is really useful. Because even for a counter, or some index, you may want to to addition and subtraction and then you definitely do not want a non-wrapping non-negative integer for intermediate results.

And the rust design with unsigned type where subtraction does not return a signed type but may fail at return or silently produce the wrong results, seems the worst possible design imaginable to me.

Was this last part added or did I just miss it? Huh.

> And the rust design with unsigned type where subtraction does not return a signed type but may fail at return or silently produce the wrong results, seems the worst possible design imaginable to me.

You can ask for whatever you meant, and indeed asking for what you meant is crucial here because if we express ourselves we get the desired results.

For example u8::borrowing_sub lets us do the arithmetic style you may have learned in primary school in which we track whether we "borrowed" one because of our subtractions, this might be useful in some places and is certainly easier to understand.

u8::checked_sub tells us either the answer or that it would overflow, which might allow us to take a different course of action and not need the subtraction.

u8::saturating_sub performs saturating arithmetic, if it would overflow we get the largest value in the appropriate direction instead, this often makes sense in e.g. signal processing.

u8::unchecked_sub promises we know the subtraction doesn't overflow and so no checks are needed, this is a performance optimisation if you really need it.

u8::wrapping_sub_signed performs the wrapping arithmetic you say is sometimes a good idea, with specifically a signed i8 parameter rather than an unsigned one if we want that.

The truth here is that you might want a lot of different operations and the C choice is not only to provide a single choice, which made a lot more sense 50+ years ago than it does today, but to provide a singularly bad default.

  • It was added, but immediately after the rest. I was just quickly refreshing my memory on what Rust was doing. I think it is a terrible design.

    If you want special function which protects you from errors in specific scenarios it is easy enough to do this in C. But I do think the C defaults are actually ok and having all the wrapper functions and boiler plate has its downsides too (What I would admit is bad in C are the default warning modes in C compilers).

    • Thanks for clarifying.

      I cannot imagine we will end up agreeing, but it's good to understand why you made that edit

The wrapping APIs do come up a lot in cryptography, but in bit twiddling I think they're as often a hindrance because we actually want to be pulled up short if we're trying to squeeze things where they won't fit.

It have definitely written C code which tries to use 257 values for a byte, with zero playing both its role as "just zero" in some places, and then also serving as 256 because "it's never zero" in other places and of course this is a nasty bug if one of those "it's never zero" zeroes gets into the "it's just zero of course" code paths or vice versa.

The "Wrapping will fix my arithmetic ordering" thing is in this article too and I think that's also a terrible idea, maybe even worse than the wrapping unsigned integer types themselves because it leads to a muddled idea of what's really going on.