> Java-style wrapping integers should never be the default, this is arguably even worse than C and C++’s UB-on-overflow which at least permits an implementation to trap.
Either way there are explicit methods for doing wrapped, checked or saturating operations in every mode.
I imagine throw-on-overflow is slower than wrap-on-overflow.
C# can be configured to throw an exception when an int is overflowed, [0] but this behaviour isn't the default and is rarely used (typically it uses wrap-on-overflow). I imagine it might have a significant performance impact, but I'm not sure.
In a language like SPARK Ada, intended for formal verification, you can insist upon a rigorous proof that unintended overflow can never occur. That isn't an option for Safe Rust, at least not without significant breakthroughs in tooling.
[0] https://docs.microsoft.com/en-us/dotnet/csharp/language-refe...
The push for safe languages is motivated by pragmatism, not theoretical purity.
It depends on the CPU: MIPS has(had if you consider MIPS dead) some integer operations which trapped in case of integer overflow AFAIK the 'trapping operations' were as fast as the non-trapping operations (when no overflow occurred of course).
Unfortunately even though RISC-V is MIPS successor, it doesn't have those trapping operations :-(
I must admit I don't understand why no modern CPU ISA has these trapping instructions: explicit checks have an 'instruction cache cost', so users won't enable them which reduces security..
An obvious example: does this code result in a divide-by-zero? (I'll use C syntax.)
int myInt = INT_MAX;
++myInt;
int myOtherInt = 1000 / myInt;
If signed overflow is permitted to result in myInt holding zero, then we have a divide-by-zero. Not the kind of thing that should be left up to the particular platform.The behaviour of your Java code does not change when you move it from a 32-bit x86 machine to a 64-bit ARM machine. That's part of the appeal of Java. The same should be true of Safe Rust.
To put that another way: Safe Rust is remarkable because of its ambition: to be a truly safe language, while also having excellent real-world performance. It seems to be succeeding in doing both, without trading off on performance (Java, Go, C#) or safety (C++, and even Ada). If it starts compromising on either dimension, it becomes 'just another language'.
Integer overflow is a "program error." This case is handled by either "default" or "enabled" overflow checking:
* If checks are "enabled", then overflow must panic
* If checks are "default", then you'll get two's compliment wrapping
For implementations, if debug_assertions are enabled, then so must overflow checking be, unless the user specifically requests otherwise.
According to these rules, rustc today has "enabled" checking when debug_assertions is on, or when the user requests it via a flag. Otherwise, it leaves it to "default." If these checks ever become cheap enough, rustc may move to "enabled" in all cases by default. We'll see if that ever happens.
Integer division in Java never results in undefined behaviour. Divide-by-zero results in an exception, as does (Integer.MIN_VALUE / -1). In C/C++, both of those operations result in undefined behaviour. Modern JVMs have to generate some additional instructions to implement this [0], but I wonder if the real-world performance penalty is that substantial.
Another example is reading an uninitialized variable, which is undefined behaviour in C/C++. Does this footgun really improve performance, with modern compilers? I don't have a solid answer here but I suspect not.
> IMHO 'integer overflow is UB' should be scrapped
The C committee is opposed to radical change, they never want to step on the toes of exotic compilers and exotic hardware architectures, so I doubt they'll ever change it. I think it would be more realistic to ask the major compiler vendors to commit to never doing anything unsafe on signed integer overflow. I believe GCC has an 'opt-in' flag for this. I wonder what the performance cost is, if any. Perhaps it breaks some optimisations, but as you say, other fast languages like Rust seem to get by fine.
Still, I agree, I prefer consistent semantics between dev/prod as much as possible. Especially since there's methods to have checked/wrapping/etc arithmetic, so I can always go to those if I want the other behavior
Thankfully, this behavior is a flag which you can personally configure to be consistent across dev/prod: https://doc.rust-lang.org/cargo/reference/profiles.html
It does make sense when interpreting wrapping checks in dev as debug assertions
More like:
> "integer overflow checks are a painful/unacceptable performance degradation for some use-cases, but we still want to cough over-/under-flow bugs during testing"
Anyway luckily you can just enable integer overflow checks in release builds, which is not a uncommon setup in use-cases like server code.
(I only have limited C experience, and only for hobby projects)
So even if you knew your specific C implementation handled overflow properly there is no way to check the flag afterwards anyway.
That said, most of the time you end up counting objects in the current address space. If you assume that there can exist no more than `SIZE_MAX` objects in memory, you can avoid many overflow checks.
With regard to C compilers, there are a few cases where a compiler performs optimizations because it assumes signed integer overflow cannot happen. This is bad but typically, compiled C behaves like the underlying platform which means signed integers wrap around. With GCC, you can enforce this behavior and make signed integer overflow a defined operation with `-fwrapv`. You can also compile your code with UBSan to get runtime checks during testing. UBSan can also check for unsigned integer overflow which is defined behavior in C. So with modern C compilers, the situation is basically the same as with Rust, Zig or other safer languages.
I would say that's very rare, only for special cases or defensive programming. Usually you either know/assert the operation will not overflow because the inputs are bounded, or you use wider types (e.g. use a int32_t when adding two int16_t).
GCC says:
> This option generates traps for signed overflow on addition, subtraction, multiplication operations.
In my experience with C (which is biased towards some specific use cases), most numbers that are likely to overflow are things like sizes and counts, which cannot meaningfully be negative anyway, so you may as well use unsigned integers for them. Other cases really do require signed integers, but for most arithmetic operations you can 'just' convert to unsigned before doing the arithmetic and then convert the result back to signed.
(Some may disagree. For instance, the Google C++ style guide [1] specifically says not to "use unsigned types to say a number will never be negative", because they want the undefined overflow behavior of signed types, in order to allow the compiler to diagnose bugs and to avoid "imped[ing] optimization". I think this is mostly nonsense; the drawbacks far outweigh the benefits, and tools for detecting overflow like UBSan can be told to check unsigned overflow as well.)
That said, even if you avoid the UB cases, checking for overflow correctly is hard; I've found many security vulnerabilities caused by missing or incorrect overflow checks. __builtin_add_overflow and friends are very nice if you have them, though unergonomic. I wish a more ergonomic version were standardized as part of the language.
Yes, I really disagree. unsigned integers mean one thing, which is "modular arithmetic". Unless you are in the very uncommon case of actually needing modular arithmetic, for instance, when implementing a crypto or hash algorithm, you want normal integers. As soon as you have anything that may have any chance of introducing a substraction somewhere, unsigned will cause bugs.
I don't know how many times I had to debug broken code such as
for(int i = 0; i < some_size - 1; i++) { ... }
because some_size was unsigned.If you really want a "number that cannot be negative", you don't wan't some_size - 1 to silently give you UINT_MAX, you want a type that will give you a compile-time or at worst run-time error.
Could you supply a non-Google link to example code? Perhaps in some git repository or a code snippet on a non-Google snippet website?
It's however zero-overhead wrt a+b plus the manual error checking you'd have to do if you wanted to be correct
Parts of postgres just heavily document the allowed range and expected domain of such functions (effectively encoding a type system into the comments and relying on a human to enforce it).
I've seen people drop down into assembly to prevent UB. Note that checking the overflow flag doesn't necessarily suffice; the problem happens during compilation -- e.g. if you write `x = (x-MIN_INT)+MAX_INT;` the compiler might be able to reason that the only value for x which wouldn't have UB is MIN_INT. Consequently the result must be MAX_INT, and the compiler can inline that constant anywhere else it's used without ever issuing the instructions so that you could check an overflow flag if you wanted to. If you do those calculations in assembly then you have a lot more freedom in that regard.
Some projects do manually check every arithmetic call (or more commonly they'll lean on the pre-processor to do that kind of busywork for them), or at least they'll do so out of some small, core kernel which is more heavily vetted and can't afford the overhead.