Re:The Value of Undefined Behavior
by arcticbull on Saturday July 21, 2018 @03:00PM
(#17583274)
Its undefined because it's not guaranteed to happen, that's the whole point, and that's what makes it undesirable. Rust has it right here: don't allow undefined behavior and make the programmer specify the behavior they want.
If they want wrapping overflow, fine. If they don't, great. Tell the compiler don't make it guess/assume/optimize/hope. What this article is arguing for IMO is not the concept of behavior that happens to sometimes work and sometimes not depending on the target machine (because thats crazy) but the functionality itself, which is valuable, and should be explicit and defined. The issue is the last-generation languages like C/C++ don't give you mechanisms to express what you want so you're left hoping the compiler figures out what you meant. That's not a world I want to live in anymore, we can do better.
UB is bad, period. Define what you want, make the compiler do it.
Re:The Value of Undefined Behavior
by obl on Saturday July 21, 2018 @03:14PM
(#17583350)
Some UB in C is largely regarded as a bad idea, such as signed overflow.
One reason for that is that it's easy to get rid of it, and guess what, we have a bunch of compiler flags just for that.
That absolutely does not mean that "UB is bad, period".
In fact you praise Rust but only safe Rust has no UB.
Actually, if they do end up putting more and more knowledge about the type system invariants in the optimizer, the rules for UB in unsafe code are going to be more complicated than C (think type-based aliasing on steroids).
Don't get me wrong, it's good design to nicely package these in unsafe code, but if your language can craft pointers and load/store from them, your spec will have to have UB (or boundcheck all loads and stores).
Re:The Value of Undefined Behavior
by DSMan195276 on Saturday July 21, 2018 @03:47PM
(#17583516)
> the rules for UB in unsafe code are going to be more complicated than C
This is one of the biggest issues I have with Rust, personally. The interaction between safe and unsafe Rust is not well defined, making it largely impossible to write 'correct' unsafe Rust that isn't broken in some weird situation, or won't ever get broken in later versions of the compiler. And since the Rust compiler has even more information then the C compiler does, it can theoretically make much crazier optimizations then it is doing right now that could break your code down the line. Like you mentioned, if it goes down that route it'll be like the strict-aliasing situation in C but times 10.
Re:The Value of Undefined Behavior
by steveklabnik on Saturday July 21, 2018 @05:14PM
(#17583879)
The interaction is well defined in the sense of “unsafe leta you do these things” but specifically, the rules about what invariants unsafe is required to uphold are not super well defined. The overall shape is there, but we’re not at an ISO standard level of definition. It’s an active area of work and research.
Re:The Value of Undefined Behavior
by jacobush on Saturday July 21, 2018 @03:58PM
(#17583563)
I wonder if it would help if there were different rules for un-safe Rust and safe Rust?
Or from a more pragmatic point of view, if one could compile un-safe Rust similarly to "optimisations off" in C compilers. No aggressive optimisation, only straight forward translation. While Rust code marked safe could be compiled with the most insane optimisations, and still be ... safe.
If not, I think people concerned about such things, might opt to write the unsafe parts on C (a known quantity) and the safe parts in Rust.
Re:The Value of Undefined Behavior
by hsivonen on Saturday July 21, 2018 @05:23PM
(#17583925)
A crate that I wrote, encoding_rs uses unsafe for performance purposes (probably a bit more than absolutely necessary). I'd be very unhappy if unsafe compiled to slower code. The unsafe bits intermingle with safe Rust to such a degree that having to write them in C would make me very unhappy.
For the curious, the performance uses of unsafe in encoding_rs are roughly in these categories:
- Omitting Unicode correctness checks (i.e. asserting that output is correct without having the standard library re-check; Unicode correctness is part of the core competence of the crate).
- Viewing buffers of u8 or u16 as buffers of usize.
- Viewing buffers of u8 or u16 as SIMD register-sized units.
- Omitting bound checks where the compiler fails to elide them and it matters for performance, especially where the performance difference matters for competitiveness with C++.
- Reinterpreting SIMD registers in a different lane configuration. (Exposes endianness but otherwise actually safe.)
- Calling intrinsics that don't really have anything unsafe about them except that Rust makes intrinsics categorically unsafe instead of deciding on a case by case basis if they need to be unsafe.
Of these, viewing buffers as buffers of ALU or SIMD register-sized units are the only cases that come near strict aliasing and could be trouble if the compiler felt eligible to reorder writes of (in the C sense) incompatible types relative to each other on the assumption that buffers of different types are disjoint.
Re:The Value of Undefined Behavior
by steveklabnik on Saturday July 21, 2018 @04:14PM
(#17583629)
That won’t work, because safe rust optimizes around rules unsafe is expected to uphold. Even a “straightforward translation” of the unsafe could still easily break invariants safe code expects.
All this stuff is why we’re working so hard on formalizing unsafe! Your parent is right that it’s a bit fuzzy right now, but in the future, we hope that it’ll be way nicer than other unsafe languages. There’s some tooling ideas...
Re:The Value of Undefined Behavior
by clarry on Sunday July 22, 2018 @02:40AM
(#17585553)
> Some UB in C is largely regarded as a bad idea, such as signed overflow.
I don't see why it's so bad. I've never really seen a legitimate use for signed overflow, and if it happens, the code is likely buggy anyway.
"I don't know how to properly check overflows so I'm just going to look and see if the number has wrapped" isn't really a good excuse, even if a few people fall for that trap. If anything, I'd rather see a simple, standard way for checking arithmetic introduced.
Re:The Value of Undefined Behavior
by kqr on Sunday July 22, 2018 @03:28AM
(#17585666)
It sounds like you are under the impression that UB simply means you can't trust signed integers to wrap. Unfortunately, it is worse than that; under surprisingly normal-looking conditions, it is not unheard of that the compiler optimises an interation check away and replaces it with constant true or false, depending on what makes the underlying internal represenation neater.
That's way beyond "can't do X"; it's "the meaning of your program suddenly ceases to exist".
Re:The Value of Undefined Behavior
by kibwen on Saturday July 21, 2018 @03:12PM
(#17583345)
Though note that Rust still has things that are definitely undefined behavior (my favorite: "Transmuting an & to &mut is always UB; No you can't do it; No you're not special" https://doc.rust-lang.org/beta/nomicon/transmutes.html ), it's just that someone has to break out the `unsafe` keyword to get it to happen, and that an error as a result of undefined behavior can always be traced back to an `unsafe` block's invariants being violated somehow.
Re:The Value of Undefined Behavior
by arcticbull on Saturday July 21, 2018 @03:14PM
(#17583351)
Definitely, but at least when you see unsafe {} you know all bets are off instead of having to assume all bets are off all the time. I do see what you mean though. The transmute thing is pretty funny imo.
Re:The Value of Undefined Behavior
by kibwen on Saturday July 21, 2018 @04:02PM
(#17583577)
Any public API using `unsafe` code that can't enforce its own unsafe invariants should (and in practice does) get marked as `unsafe` itself in order to force callers to use `unsafe` when calling that function. Of course it's something that, by definition, the compiler cannot enforce on its own and hence requires social norms and social pressure to enforce, and it's therefore fortunate that the Rust community takes it very seriously.
Re:The Value of Undefined Behavior
by lomnakkus on Sunday July 22, 2018 @02:56AM
(#17585591)
Not that this "makes the point", necessarily, but GHC Haskell does have the whole Safe/Unsafe Haskell module inference/guarantee, so it may be a bit easier to avoid unexpectedly unsafe behavior entirely.
I have no idea whether any equivalent exists for Rust.
Re:The Value of Undefined Behavior
by dbaupp on Sunday July 22, 2018 @09:34PM
(#17589910)
There's not a module-level Safe/Trustworthy/Unsafe division, but as steveklabnik1 says, any potentially-dangerous code has to be placed into an `unsafe` block.
One can also forbid the use of any `unsafe` blocks for a crate/module (with the #[forbid(unsafe_code)] attribute), but this is slightly different to the {-# LANGUAGE Safe #-} pragma in that it does consider imports at all. However, since "unsafety" is modeled in the language in Rust, and is granular, people are encouraged to have every module be "Trustworthy" (in Safe Haskell terms): if an individual function could cause undefined behaviour if misused, it should be marked "unsafe", and a "Safe" module (i.e. one using the forbid above) won't be able to use it.
Re:The Value of Undefined Behavior
by cperciva on Saturday July 21, 2018 @05:20PM
(#17583913)
Rust has it right here: don't allow undefined behavior and make the programmer specify the behavior they want.
If they want wrapping overflow, fine. If they don't, great.
What if I know overflow can't happen (e.g. I have an integer counting from 1 to 10)? How do I say "it doesn't matter how you handle overflow; just do whatever is fastest"?
Re:The Value of Undefined Behavior
by steveklabnik on Saturday July 21, 2018 @06:01PM
(#17584079)
The parent spoke a bit too broadly. Here’s the rules:
* overflow is a “program error”, not UB
* compilers must either check for overflow and panic, or two’s compliment wrap
* when debug_assertions are on, implementations are required to panic on overflow
This means release mode wraps today, but if it’s cheap enough someday, we could make it panic.
If you want specific semantics on overflow, then you should use the various wrappers and/or methods that let you do them directly, rather than relying on any of the above. But you don’t have to specify; by default, the above is the semantics.
Re:The Value of Undefined Behavior
by tolmasky on Saturday July 21, 2018 @05:57PM
(#17584068)
In a language with a sufficiently advanced type system you can use Peano numbers to specify a type specifying an integer between 0 and 10. These type systems usually are smart enough to have optimized internal representation of said types.
Re:The Value of Undefined Behavior
by tolmasky on Saturday July 21, 2018 @06:05PM
(#17584092)
I would love to have something that would translate a more human readable version that looks like a constraint or range that internally translates to the same functionality of the peano solution. If that's what you're saying Rust intends to have then I'm very excited and continue to be impressed by the progress there.
Re:The Value of Undefined Behavior
by steveklabnik on Saturday July 21, 2018 @06:36PM
(#17584204)
https://github.com/rust-lang/rfcs/blob/master/text/2000-cons... is the current accepted design; we plan to go further in the future, but wanted something minimal at first. This is likely to land in nightly by the end of the year and be stable sometime early next year.
Re:The Value of Undefined Behavior
by red75prime on Sunday July 22, 2018 @03:09AM
(#17585621)
It cannot be done in compile-time in general case. Rice's theorem forbids. Every non-trivial property of a program (like whether result of this function lies in this range for all inputs in that range) is undecidable.
ETA: I'm not completely correct. Ada could have used something akin to 2-complement overflow handling, that is forcing every operation on a range to have a result in the same range, but it introduces runtime overhead too.
Re:The Value of Undefined Behavior
by samatman on Saturday July 21, 2018 @02:25PM
(#17583098)
This is a fine opportunity to link to one of my favorite essays on the subject, Undefined Intimacy With the Machine: http://thoughtmesh.net/publish/367.php
This argues that it is precisely C's undefined behavior which allowed for its dominance.
Re:The Value of Undefined Behavior
by arcticbull on Saturday July 21, 2018 @03:10PM
(#17583338)
I have a hard time accepting the premise that a language is better because you have to hope that it does what you want instead of telling it, and for that reason it has become successful.
In fact, the ANSI C spec even states from your link that "Undefined behavior gives the implementer license not to catch certain program errors that are difficult to diagnose." That's just not an acceptable tradeoff anymore -- in fact I don't know it ever was. That's akin to shipping your org chart because you can't make your program do what everyone agrees it should do. You know, catch programming errors, in this case.
Re:The Value of Undefined Behavior
by samatman on Saturday July 21, 2018 @03:13PM
(#17583346)
The paper argues that some behaviors being undefined let compilers target the widely varying architectures of the early microcomputer era, while still using a language that remains at least somewhat portable between them.
Re:The Value of Undefined Behavior
by arcticbull on Saturday July 21, 2018 @03:15PM
(#17583356)
Yes, but your program isn't doing what you think you told it to, which is worse, because the compiler tells you that it will, know what I mean? A false guarantee of safety is kind of the worst.
Re:The Value of Undefined Behavior
by DSMan195276 on Saturday July 21, 2018 @03:40PM
(#17583475)
> Yes, but your program isn't doing what you think you told it to,
Your program is still doing what you told it to if you weren't relying on any undefined behavior (Which, obviously, you shouldn't be). That's the point - if you write C that doesn't rely on any UB, then it can run on a large variety of architectures, work exactly the same on each one, and still get close to the maximum speed for each architecture because they can take the fastest possible option for any of the UB cases (And your program shouldn't care what they do in that situation).
Re:The Value of Undefined Behavior
by fiddlerwoaroof on Saturday July 21, 2018 @04:11PM
(#17583615)
The problem I generally have with C is that the scope of UB is really wide. Languages like Common Lisp have UB too, but it’s generally easier to avoid in “normal” code.
Re:The Value of Undefined Behavior
by dfox on Saturday July 21, 2018 @04:41PM
(#17583767)
In CL case the "is an error" wording generally covers all of C's "implementation-defined", "unspecified" and "undefined". And surprising amount of normal looking constructs are in the implementation-defined category.
When you add type declarations and (optimize (safety 0)) into the mix you get similar scope of UB as in C (another thing is that in reality nobody actually writes code like that)
Re:The Value of Undefined Behavior
by dfox on Saturday July 21, 2018 @05:46PM
(#17584032)
I think that modifiing reader literal is an error mostly to allow implementations to store such pairs in read-only pages (eg. in .rodata section when you build standalone executables) and to allow simple implementation of CDR-coding for reader literals (dfsch has three distinct implementations of abstract <pair> class for exactly this reason and to be able to actually signal meaningful error condition when you violate this restriction).
Re:The Value of Undefined Behavior
by fiddlerwoaroof on Saturday July 21, 2018 @05:54PM
(#17584057)
But it’s not an error: for some of these cases the standard doesn’t specify the consequences and most implementations don’t throw an error if you modify a reader literal, even if you end up modifying the interned list.
Re:The Value of Undefined Behavior
by dfox on Saturday July 21, 2018 @06:11PM
(#17584115)
"Is an error" is wording used by the standard for things that are left undefined. For things that should signal some kind of error condition the standard uses wording "an error is signalled", the set of such cases is surprisingly small.
Re:The Value of Undefined Behavior
by gpderetta on Sunday July 22, 2018 @08:33AM
(#17586419)
If you are hitting UB, then you haven't really told the compiler anything. Do what I mean is not really a thing yet. The compiler can't read the programmer head.
Re:The Value of Undefined Behavior
by mehrdadn on Saturday July 21, 2018 @05:59PM
(#17584071)
To those annoyed about, say, signed integer overflow being undefined: do you even have a habit of ensuring that what's inside your loop will behave correctly around an overflow boundary? Is the real problem really only the loop increment for you?
Re:The Value of Undefined Behavior
by 3pt14159 on Saturday July 21, 2018 @06:01PM
(#17584082)
The problem is tractability. If you send me your data, your source, and your OS and if I compile it with a slightly different compiler and I can't reproduce it gets incredibly frustrating to hunt down why something isn't working.
Re:The Value of Undefined Behavior
by mehrdadn on Saturday July 21, 2018 @10:29PM
(#17584977)
Interesting, this is a less common complaint than what I usually hear. It kind of makes sense if you run into this a lot (I don't in C++, but maybe it happens more in C?), but at the same time I'm wondering why can't you just e.g. use -fwrapv if you want to rely on integer wrapping in your release executable? It also doesn't sound like it's related to using a different compiler, since using the same compiler with a flag like -fwrapv or with optimizations disabled should also prevent UB from being exploited.
Re:The Value of Undefined Behavior
by makecheck on Saturday July 21, 2018 @06:27PM
(#17584163)
Excessive flexibility for optimizers, schedulers, etc. adds a high price to debugging, and it’s not like we have perfect programs that can only be improved in these fancier ways.
Programs are not all written by experts. Performance may be left on the table in areas that have nothing to do with optimizers or schedulers (e.g. picking an absolutely terrible algorithm or not noticing unnecessary memory allocations).
If something must remain “unspecified”, I at least want a debugging tool for that behavior. If something “may” happen, give me a switch to make it happen. If something is data-dependent, show me the data that triggers a different outcome or give me a way to scramble data enough to increase the chance of triggering a change in behavior.
Re:The Value of Undefined Behavior
by taneq on Saturday July 21, 2018 @03:07PM
(#17583325)
"Value" is the wrong word, because it implies goodness. It's taken me a while to come around to this viewpoint but I think "undefined behaviour" is unambiguously bad in pretty much every context. I understand why it was originally included in language specs but in modern times, where even the meanest embedded processors are generally 32 bit 2's complement processors with an ALU, we can afford to define the behaviour of the language which we're using.
Re:The Value of Undefined Behavior
by ben-schaaf on Sunday July 22, 2018 @01:31AM
(#17585409)
Instead of defining what happens in the situation, most languages opt for making this type of error impossible in the first place. Languages with a GC do this by never freeing if a reference still exists. Rust (safe) does this by enforcing lifetimes at compile time.
Re:The Value of Undefined Behavior
by millstone on Sunday July 22, 2018 @03:20AM
(#17585647)
These languages aren't capable of expressing the software that's routinely written in C (inc C++). For example, there's no way to enforce the lifetimes of NaN-boxed pointers managed by the multithreaded GC in the custom heap of your browser's JS engine.
There's certain classes of C's UB that can be addressed, but most of it can never be without neutering C to the point of uselessness.
Re:The Value of Undefined Behavior
by taneq on Saturday July 21, 2018 @11:26PM
(#17585138)
In a perfect world it would either be impossible by design or result in a compile error. I'd settle for "reliably crash with an error message saying where in the code it happened".
Re:The Value of Undefined Behavior
by taneq on Sunday July 22, 2018 @04:20AM
(#17585819)
True, you can even macro it for more convenience. I'm more worried about things where some obscure edge case causes the compiler to optimise away something important because it's deemed to be UB.
Re:The Value of Undefined Behavior
by bluecalm on Sunday July 22, 2018 @08:40AM
(#17586436)
Obviously you check for NULL first. If something may or may not exists you first ask if it exists (check for NULL) and then of it exists you do something with it. When you free something you destroy it, that's why it's useful to set the pointer which before pointed to it to NULL.
Re:The Value of Undefined Behavior
by lomnakkus on Friday July 27, 2018 @03:08PM
(#17628363)
I just (well, a few days ago) explained to you that your suggested "solution" would result in UB.
Then you come up with the "solution" that everybody must always check for NULL pointers before dereferencing them. If everybody always did that we might as well just define the behavior of reference-after-free, because what you said either a) requires you to keep a reference count (or null-check before every deref which may be even more expensive) at runtime(!), or b) requires a program-level proof.
So... no. What you suggested is not a solution in any meaningful sense.
Re:The Value of Undefined Behavior
by MrBingley on Sunday July 22, 2018 @02:02AM
(#17585473)
The problem with the first example is that `int` and `unsigned` are not the correct types to be using for array indexing. As we saw, they were 32 bits wide on a 64 bit platform, which lead to an additional sign extension and (in the case of `unsigned`) truncation instruction. However, this can be avoided by using `ptrdiff_t` and `size_t`, which are defined to match the platform size and avoid the extension and truncation altogether. The fact that signed integer overflow being undefined allowed the compiler to elide the `int` truncation is somewhat irrelevant, since using the correct integer types leads to even faster code that doesn't rely on undefined behaviour at all.
Re:The Value of Undefined Behavior
by saagarjha on Saturday July 21, 2018 @10:41PM
(#17585010)
> What irritates a lot of people is that compilers will still apply the strict aliasing rule even when it’s trivial for the compiler to prove that aliasing is occurring
I think better diagnostics for this would be a win for everyone.
Re:The Value of Undefined Behavior
by swayvil on Saturday July 21, 2018 @08:40PM
(#17584654)
Defined behavior is a mere shadow of undefined behavior. All form comes from the constraining definition. One who limits his explorations to the defined has crawled into a hole and pulled in the hole after him.
Re:The Value of Undefined Behavior
by Dylan16807 on Sunday July 22, 2018 @05:16AM
(#17585959)
* Most forms of undefined behavior don't have any meaningful use to compilers.
* With integer overflow it would be relatively easy to write a spec that allows "n+1>n" optimizations but not utterly anything to happen. With strict aliasing it's probably not too hard either. You'd still see some program misbehavior in the strict aliasing case, but it would be a vastly reduced set of misbehavior. And allowing signed numbers to act more like abstract numbers would often decrease misbehavior.
Re:The Value of Undefined Behavior
by iainmerrick on Saturday July 21, 2018 @05:33PM
(#17583980)
If these are the best justifications we can come up with, it doesn't change my opinion that the current reliance on UB by compilers is a bad idea. Anyone got any better ones?
Re:The Value of Undefined Behavior
by Asooka on Saturday July 21, 2018 @04:57PM
(#17583831)
I really think that undefined behaviour should be switched to machine-dependent behaviour everywhere and we should just have tools that can suggest optimized variants of hot functions - to be rewritten in ways that take advantage of assuming undefined behaviour doesn't happen. Or lift the `extern "lang"{}' construct from C++ to C and have `extern "C-strict"' which turns on full strict undefined behaviour optimizations. Yes, I know I can turn most of them off. We ship C++ software and always compile with singed-overflow enabled and strict-aliasing disabled (among others). Those are just too dangerous to be turned on for an entire codebase of a large C++ application where you have no idea what will be inlined where and what LTO will do. But if some UB allows for measurable performance gains, we should be able to turn it back on for restricted safe scopes.
And please, if the compiler can prove that the program invokes UB in "strict-UB" mode, that should be an error, not a cause for the code to be deleted. I'm talking about the case of this SPEC 2006 code [1]
It doesn't make sense to me to just apply potentially disastrous micro-optimizations that save a couple of percent of run-time on the whole program. This sounds like a classic case of optimizing before measuring.
If they want wrapping overflow, fine. If they don't, great. Tell the compiler don't make it guess/assume/optimize/hope. What this article is arguing for IMO is not the concept of behavior that happens to sometimes work and sometimes not depending on the target machine (because thats crazy) but the functionality itself, which is valuable, and should be explicit and defined. The issue is the last-generation languages like C/C++ don't give you mechanisms to express what you want so you're left hoping the compiler figures out what you meant. That's not a world I want to live in anymore, we can do better.
UB is bad, period. Define what you want, make the compiler do it.
One reason for that is that it's easy to get rid of it, and guess what, we have a bunch of compiler flags just for that.
That absolutely does not mean that "UB is bad, period".
In fact you praise Rust but only safe Rust has no UB.
Actually, if they do end up putting more and more knowledge about the type system invariants in the optimizer, the rules for UB in unsafe code are going to be more complicated than C (think type-based aliasing on steroids).
Don't get me wrong, it's good design to nicely package these in unsafe code, but if your language can craft pointers and load/store from them, your spec will have to have UB (or boundcheck all loads and stores).
This is one of the biggest issues I have with Rust, personally. The interaction between safe and unsafe Rust is not well defined, making it largely impossible to write 'correct' unsafe Rust that isn't broken in some weird situation, or won't ever get broken in later versions of the compiler. And since the Rust compiler has even more information then the C compiler does, it can theoretically make much crazier optimizations then it is doing right now that could break your code down the line. Like you mentioned, if it goes down that route it'll be like the strict-aliasing situation in C but times 10.
Not contesting the claim, but what in the interaction specifically is not well defined?
Or from a more pragmatic point of view, if one could compile un-safe Rust similarly to "optimisations off" in C compilers. No aggressive optimisation, only straight forward translation. While Rust code marked safe could be compiled with the most insane optimisations, and still be ... safe.
If not, I think people concerned about such things, might opt to write the unsafe parts on C (a known quantity) and the safe parts in Rust.
For the curious, the performance uses of unsafe in encoding_rs are roughly in these categories:
- Omitting Unicode correctness checks (i.e. asserting that output is correct without having the standard library re-check; Unicode correctness is part of the core competence of the crate).
- Viewing buffers of u8 or u16 as buffers of usize.
- Viewing buffers of u8 or u16 as SIMD register-sized units.
- Omitting bound checks where the compiler fails to elide them and it matters for performance, especially where the performance difference matters for competitiveness with C++.
- Reinterpreting SIMD registers in a different lane configuration. (Exposes endianness but otherwise actually safe.)
- Calling intrinsics that don't really have anything unsafe about them except that Rust makes intrinsics categorically unsafe instead of deciding on a case by case basis if they need to be unsafe.
Of these, viewing buffers as buffers of ALU or SIMD register-sized units are the only cases that come near strict aliasing and could be trouble if the compiler felt eligible to reorder writes of (in the C sense) incompatible types relative to each other on the assumption that buffers of different types are disjoint.
All this stuff is why we’re working so hard on formalizing unsafe! Your parent is right that it’s a bit fuzzy right now, but in the future, we hope that it’ll be way nicer than other unsafe languages. There’s some tooling ideas...
I don't see why it's so bad. I've never really seen a legitimate use for signed overflow, and if it happens, the code is likely buggy anyway.
"I don't know how to properly check overflows so I'm just going to look and see if the number has wrapped" isn't really a good excuse, even if a few people fall for that trap. If anything, I'd rather see a simple, standard way for checking arithmetic introduced.
That's way beyond "can't do X"; it's "the meaning of your program suddenly ceases to exist".
I don't think that's a particularly useful point, as is.
I have no idea whether any equivalent exists for Rust.
One can also forbid the use of any `unsafe` blocks for a crate/module (with the #[forbid(unsafe_code)] attribute), but this is slightly different to the {-# LANGUAGE Safe #-} pragma in that it does consider imports at all. However, since "unsafety" is modeled in the language in Rust, and is granular, people are encouraged to have every module be "Trustworthy" (in Safe Haskell terms): if an individual function could cause undefined behaviour if misused, it should be marked "unsafe", and a "Safe" module (i.e. one using the forbid above) won't be able to use it.
So the attitude of "only unsafe programs have UB by the Rust spec" effectively means "all programs have UB by the Rust spec"
If they want wrapping overflow, fine. If they don't, great.
What if I know overflow can't happen (e.g. I have an integer counting from 1 to 10)? How do I say "it doesn't matter how you handle overflow; just do whatever is fastest"?
* overflow is a “program error”, not UB
* compilers must either check for overflow and panic, or two’s compliment wrap
* when debug_assertions are on, implementations are required to panic on overflow
This means release mode wraps today, but if it’s cheap enough someday, we could make it panic.
If you want specific semantics on overflow, then you should use the various wrappers and/or methods that let you do them directly, rather than relying on any of the above. But you don’t have to specify; by default, the above is the semantics.
In the future we’ll have const generics that let you express it directly without that shenanigans.
ETA: I'm not completely correct. Ada could have used something akin to 2-complement overflow handling, that is forcing every operation on a range to have a result in the same range, but it introduces runtime overhead too.
This argues that it is precisely C's undefined behavior which allowed for its dominance.
In fact, the ANSI C spec even states from your link that "Undefined behavior gives the implementer license not to catch certain program errors that are difficult to diagnose." That's just not an acceptable tradeoff anymore -- in fact I don't know it ever was. That's akin to shipping your org chart because you can't make your program do what everyone agrees it should do. You know, catch programming errors, in this case.
Your program is still doing what you told it to if you weren't relying on any undefined behavior (Which, obviously, you shouldn't be). That's the point - if you write C that doesn't rely on any UB, then it can run on a large variety of architectures, work exactly the same on each one, and still get close to the maximum speed for each architecture because they can take the fastest possible option for any of the UB cases (And your program shouldn't care what they do in that situation).
When you add type declarations and (optimize (safety 0)) into the mix you get similar scope of UB as in C (another thing is that in reality nobody actually writes code like that)
Programs are not all written by experts. Performance may be left on the table in areas that have nothing to do with optimizers or schedulers (e.g. picking an absolutely terrible algorithm or not noticing unnecessary memory allocations).
If something must remain “unspecified”, I at least want a debugging tool for that behavior. If something “may” happen, give me a switch to make it happen. If something is data-dependent, show me the data that triggers a different outcome or give me a way to scramble data enough to increase the chance of triggering a change in behavior.
There's certain classes of C's UB that can be addressed, but most of it can never be without neutering C to the point of uselessness.
If you never reuse addresses, long running programs will have some trouble ahead.
Oh, it's not even that simple because you'd have to make access to the region atomic.
Then you come up with the "solution" that everybody must always check for NULL pointers before dereferencing them. If everybody always did that we might as well just define the behavior of reference-after-free, because what you said either a) requires you to keep a reference count (or null-check before every deref which may be even more expensive) at runtime(!), or b) requires a program-level proof.
So... no. What you suggested is not a solution in any meaningful sense.
I think better diagnostics for this would be a win for everyone.
Ya, I know, offtopic.
* With integer overflow it would be relatively easy to write a spec that allows "n+1>n" optimizations but not utterly anything to happen. With strict aliasing it's probably not too hard either. You'd still see some program misbehavior in the strict aliasing case, but it would be a vastly reduced set of misbehavior. And allowing signed numbers to act more like abstract numbers would often decrease misbehavior.
Magic behavior of "int" (but not "unsigned int") --> just use a type that's the correct size and better conveys your intention, like "size_t".
(And that one doesn't make your code "many times faster", in the example given it saves a single instruction.)
Type-based assumptions about aliasing (with a special case for char*) --> be explicit about your intention with the "restrict" keyword.
Saving one instruction was probably worth it in the 70s, when security wasn't a thing, but it definitely isn't now.
And please, if the compiler can prove that the program invokes UB in "strict-UB" mode, that should be an error, not a cause for the code to be deleted. I'm talking about the case of this SPEC 2006 code [1]
It doesn't make sense to me to just apply potentially disastrous micro-optimizations that save a couple of percent of run-time on the whole program. This sounds like a classic case of optimizing before measuring.
1 https://blog.regehr.org/archives/918
But you have done the measurements, right?
Also compilers normally do not prove that programs hit UB, quite the contrary. Doing that statically is, in general, impossible.