by tialaramex | 2025-09-09 21:15:40+00:00
So funny thing, although I do periodically re-read older material I've written on Hacker News among other places as I've discussed elsewhere, I wouldn't ordinarily look at something like this after it's drifted off my first page until perhaps a month or more have passed. However, this afternoon Youtube's feed suggested I watch a video about exactly this topic by you, and it was pretty obviously a direct response to my comment written a few days after the comment so of course I checked here.

Lets do some high level corrections first, after all you might learn something and if not perhaps others will.

Data Races are not just race conditions. Race conditions are an ordinary part of our lived experience, the example I usually give is putting a cat out of your front door, then walking to the kitchen to close that door too, the cat might race around the outside and enter before you close the other door, and that's a race condition. To defeat this particular race, close other doors (and windows) first. A particularly nasty race condition seen in modern systems is the TOCTOU race, where the programmer checks something is true and later acts on that check but it is perhaps no longer true. Modern Unix systems have several features that exist mainly to prevent TOCTOU races in storage, such as the ...at family of filesystem calls.

Data Races are something much stranger, in my cat analogy Data Races are as if both Alice and Bob were able to pick up the same cat, Alice puts it out in the front garden, Bob puts it in the back garden, at the same time - now where is the cat? The situation is nonsensical even before we try to wrestle with its consequences. But data races are real.

Safe Rust doesn't have Data Races. By the SC/DRF theorem this means Safe Rust is Sequentially Consistent, which is the property humans need to be able to reason about non-trivial concurrent software. Now, you don't have to go this route, the easiest way to get SC is to write serial software, you can't have a data race if your program doesn't have any concurrency. But languages like C or C++ just give up and say well, data races mean UB. That's the default if you have concurrency and can't prove data race freedom. All bets are off, you lose, go home.

The C++ 26 initialization Erroneous Behaviour was precisely my point. You've greatly misunderstood the consequence of it previously being Undefined Behaviour here. Since it makes it clearer to see what's going on lets work entirely with C++ 26 semantics.

In C++ 26 `int k;` gets you a signed integer local variable named k which has some definite value of its type, the compiler has chosen what that value is, maybe it's six. It can't be negative infinity, or pi, or "Odin sucks" none of those are signed integer values. Evaluating k is Erroneous, tooling should (if able) alert you to your mistake, for example by terminating the program but even if it doesn't or can't, there was some definite value (maybe six).

Meanwhile in C+ 26 `int j [[indeterminate]];` gets you a signed local variable named j which does not have any definite value. This was also the consequence your definition of k would have had in earlier versions of C++. Evaluating j is UB. Absolutely anything might happen.

Why would we want this feature? Isn't it just worse? After all, C++ 26 adds a whole language attribute to preserve it, they must have some reason to do so? Rust goes much further, it has an entire type MaybeUninit<T> which exists to enable such dangerous behaviour, so clearly it's valuable enough to go write a lot of code.

Compiler Optimisation. The optimisation passes change your program with the intent to make it faster or smaller. They're obliged to preserve defined semantics when doing this. But they are not obliged to preserve any other properties, after all they were never defined. In your division by zero example, choosing to trap means that Odin has to emit code to check for zero and trap if that's not hardware behaviour. But in a language with UB for division by zero they don't need to emit the check. So hence UB is a potential for compiler optimisation.

You can also sidestep the check with a type system of course. For example Rust doesn't need a division by zero check when the divisor was a NonZero type, those are by definition never zero and so it's safe not to check.

> Why is not just shitty code?

If you prefer, Odin is shitty code. I think that's probably less helpful to anybody who is wondering about it than a more detailed explanation, but if you prefer to write "Odin is shitty code" in Odin's description and FAQ I guess that covers it and you'd see much less criticism on this topic.

But I suspect you don't mean that, which gets to the heart of the issue - which code was shitty? In Rust this is also well defined, and that might unlock for you why so many people like Rust so much. Instead of back-and-forth arguments about whether Jenny's code is wrong for calling Ralph's function with an inappropriate parameter, or whether Ralph's function is wrong for not handling Jenny's call, Rust chooses to nail this down in the language.

Exploring this in more detail led to me writing my first public Rust crate, named misfortunate.

> My comments on Twitter or other social medias should not be taken as official in any way

That's just not how anything works. If you said it then that's what you said.