But if all you want is check + crash when that happens then can't you just use a compiler flag or a different data type or something else for that in C++?
Different type? Sure. Disadvantage is that it’s not pervasive. Compiler flag? Probably, I am not an expert on compiler flags that change language semantics; we don’t do that in Rust, but I know C and C++ compilers do.
> Compiler flag? Probably, I am not an expert on compiler flags that change language semantics; we don’t do that in Rust, but I know C and C++ compilers do.
* When debug_assertions is on, it's in "enabled" mode.
* Otherwise, it’s in “default” mode.
* -C overflow-checks turns on "enabled" mode, regardless of other settings.
This is completely consistent with the rules of the language. If these checks ever get cheap enough, rustc may even start to turn them on by default, which is also acceptable under these rules. We'll see if that ever happens, though.
So let's review where we are right now. C++ left the entire thing undefined, which gave compilers the complete freedom to add flags that define e.g. wrap-around or trapping or other behavior. I suggested you can use such flags if you want. You complained about that and said it's "changing" language semantics... despite the fact that it's 100% consistent with the "rules of the language". You even went on to claim "we don't do that in Rust". All right. So I pointed you to a Rust compiler flag that changes the default wrap-around to a panic. Now you're saying that's not changing Rust's language semantics... and your defense is "it's consistent with the rules of the language"...?!
Undefined behavior is different from implementation defined behavior. Code that exhibits UB is not a valid program. Flags that take UB and define it are categorically different than flags that tweak various options, because flags that define UB expand the set of valid programs, where flags that tweak options do not.
Okay, you're very confused. You're getting it almost backwards.
Undefined behavior is behavior that the language does not define, not behavior that the implementation is prohibited from defining. That's why you can't treat it like (for example) a random-number generator. Implementations are well within their rights (i.e. 100% consistent with the language) to define previously-undefined behavior to be anything. They're also just as welcome to leave them undefined. Both are 100% consistent with the language and neither is "changing" the language semantics. No program that exhibited UB is going to misbehave somehow just because someone decided to define the behavior under UB. The semantics already allowed anything to happen. Whatever they define falls under "anything could happen".
Implementation-behavior is behavior that the the implementation is guaranteed to define. Like with UB, the implementation has freedom to choose a behavior. Unlike with UB, it is not allowed to leave that behavior undefined. So the program can be sure to have a well-defined output.
It is completely wrong to simultaneously say changing "wrap" to "crash" is "not changing semantics" and somehow "consistent" with the language rules, but that changing "undefined" to "wrap" is "changing semantics" and "not consistent" with the language rules. If the language wraps on overflow, then changing that to a panic is actively shrinking the set of valid programs; valid programs that used to behave one way now behave differently. (They crash!) The latter is merely expanding the set of valid programs; programs that had no rights to claim any behavior under UB now actually have a right to claim something under the implementation, but that doesn't change the behavior of previously-valid programs. They're still provided exactly the same guarantees they already were.
(P.S., UB isn't even a property of a program, but of an execution. But I'll leave that out since the simplified version is clearly confusing enough as-is.)