From what I can tell, the only significant difference between C and Odin mentioned in the post is that Odin zero-initializes everything whereas C doesn't. This is a fundamental limitation of C but you can alleviate the pain a bit by writing better primitives for yourself. I.e., you write your own allocators and other fundamental APIs and make them zero-initialize everything.
So one of the big issues with C is really just that the standard library is terrible (or, rather, terribly dated) and that there is no drop-in replacement (like in Odin or Rust where the standard library seems well-designed). I think if someone came along and wrote a new C library that incorporates these design trends for low-level languages, a lot of people would be pretty happy.
[1]: https://www.rfleury.com/p/untangling-lifetimes-the-arena-all...
I suppose glib comes the closest to this? At least the closest that actually sees fairly common usage.
I never used it myself though, as most of my C has been fairly small programs and I never wanted to bother people with the extra dependency.
Why has nobody come along and created an alternative standard library yet? I know this would break lots of things, but it’s not like you couldn’t transition a big ecosystem over a few decades. In the same time, entire new languages have appeared, so why is it that the C world seems to stay in a world of pain willingly?
Again, mind you, I’m watching from the outside, really just curious.
I tried to create my own alternative about a decade ago which eventually influenced my other endeavours.
But another big reason is that people use C and its stdlib because that's what it is. Even if it is bad, its the "standard" and trivially available. Most code relies on it, even code that has its own standard library alternative.
Everybody has created their own standard library. Mine has been honed over a decade, why would I use somebody else's? And since it is designed for my use cases and taste, why would anyone use mine?
Because people are so terribly opinionated that the only common denominator is that the existing thing is bad. For every detail that somebody will argue a modern version should have, there will be somebody else arguing the exact opposite. Both will be highly opinionated and for each of them there is probably some scenario in which they are right.
So, the inability of the community to agree on what "good" even means, plus the extreme heterogenity of the use cases for C is probably the answer to your question.
Probably, IMO, because not enough people would agree on any particular secondary standard such that one would gain enough attention and traction¹ to be remotely considered standard. Everyone who already has they own alternatives (or just wrappers around the current stdlib) will most likely keep using them unless by happenstance the new secondary standard agrees (by definition, a standard needs to be at least somewhat opinionated) closely with their local work.
Also, maintaining a standard, and a public implementation of it, could be a faffy and thankless task. I certainly wouldn't volunteer for that!
[Though I am also an outsider on the matter, so my thoughts/opinions don't have any particular significance and in insider might come along and tell us that I'm barking up the wrong tree]
--------
[1] This sort of thing can happen, but is rare. jquery became an unofficial standard for DOM manipulation and related matters for quite a long time, to give one example - but the gulf between the standard standard (and its bad common implementations) at the time and what libraries like jquery offered was much larger than the benefits a secondary C stidlib standard might give.
(But I think Odin is great!)
Instead, data needs to be viewed more abstractly. Yes, it will eventually manifest in memory as bytes in some memory cell, but how that's layouted and moved around is not the concern of you as the programmer that's a user of data types. Looking at some object attributes foo.a or foo.b is just that - the abstract access of some data. Whether a and b are adjacent in memory should be insubstantial or are even on the same machine or are even backed by data cells in some physical memory bank. Yes, in some very specific (!) cases, optimizing for speed makes it necessary to care about locality, but for those cases, the language or library need to provide mechanisms to specify those requirements and then they will layout things accordingly. But it's not helpful if we all keep writing in some kind of glorified assembly language. It's 2025 and "data type" needs to mean something more abstract than "those bytes in this order layed out in memory like this", unless we are writing hand-optimized assembly code which most of us never do.
> Yes, it will eventually manifest in memory as bytes in some memory cell...
So people view a program how the computer actually deals with it? And how they need to optimize for since they are writing programs for that machine?
So what is an example of you abstraction that you are talking about? Is there a language that already exists that is closer to what you want? Otherwise you are talking vaguely and abstractly and it doesn't really help anyone understand your point of view.
DIDNT SHIFT UP
But there is a fine line between having general understanding of the details of what's going on inside your system and using that knowledge to do very much premature optimizations and getting stuck in a corner that is hard to get out of. Large parts of our industry are in such a corner.
It's fun to nerd out about memory allocators, but that's not contributing to overall improvements of software engineering as a craft which is still too much ad hoc hacking and hoping for the best.
I'm sorry, but it is. Understanding memory layout and (sometimes) using custom allocators can improve performance in ways that no compiler cannot do automatically. And when used correctly they contribute to code correctness too. This is the reason they are used in projects like Chromium.
Regarding your car analogy, you have to remember that developers here are not driving the car: they're building the car. Understanding the differences between and ICE engine and an electric motor are very salient to designing a good car.
Actually I do, and I include the inertia and momentum of every piece of the drive-train as well, and the current location of the center of gravity. I'm thinking about all of these things through the next predicted 5 seconds or so at any given time. It comes naturally and subconsciously. To say nothing of how you really aren't going to be driving a standard transmission without that mental model.
Your analogy is appropriate for your standard American whose only experience with driving a car is the 20 minute commute to work in an automatic, and thus more like a hobbyist programmer or a sysadmin than someone whose actual craft is programming. Do you really think truckers don't know in their gut what their fuel burn rate is based on how far they've depressed the pedal?
There is no instead here. This is not a choice that has to be made once and for all and there is no correct way to view things.
Languages exist if you want to have a very abstract view of the data you are manipulating and they come with toolchains and compilers that will turn that into low level representation.
That doesn’t preclude the interest of languages which expose this low level architecture.
It's obviously not for the low level parts of the toolchain which are required to make very abstract languages work.
Not everybody needs to worry about L2 or L3 most of the time, but if you are using a systems-level programming language where it might be of a concern to you at some point, it's extremely useful to be able to have that control.
> expecting every programmer to call malloc and free in the right order
The point of custom allocators is to not need to do the `malloc`/`free` style of memory allocation, and thus reduce the problems which that causes. And even if you do still need that style, Odin and many other languages offer features such as `defer` or even the memory tracking allocator to help you find the problems. Just like what was said in the article.
GHC, which is without a doubt the smartest compiler you can get your grubby mitts on, is still an extremely stupid git that can't be trusted to do basic optimizations. Which is exactly why it exposes so many special intrinsic functions. The "sufficiently smart compiler" myth was thoroughly discounted over 20 years ago.
Unfortunately, that's not possible. The optimal data layout depends on the order that data is accessed, which isn't knowable without knowing all possible ways the program could execute on all possible inputs.
ECS is vastly superior as an abstraction that pretty much everything that we had before in games. Tightly coupled inheritance chains of the 90s/2000s were minefields of bugs.
Of course perhaps not every type of app will have the same kind of goldilocks architecture, but I also doubt anyone will stumble into something like that unless they’re prioritizing it, like game programmers did.
But I agree that DOD in practice is not a compromise between performance and ergonomics, and Odin kind of shows how that is possible.
Cache is easily the most important consideration if you intend to go fast. The instructions are meaningless if they or their dependencies cannot physically reach the CPU in time.
The latency difference between L1/L2 and other layers of memory is quite abrupt. Keeping workloads in cache is often as simple as managing your own threads and tightly controlling when they yield to the operating system. Most languages provide some ability to align with this, even the high level ones.
Andrew Kelley rewrote the Zig compiler in that style and got great speedups.
data is bytes. period. your suggestion rests on someone else seeing how it is the case and dealing with it to provide you with ways of abstraction you want. but there is an infinity of possible abstractions – while virtual memory model is a single solid ground anyone can rest upon. you’re modeling your problems on a machine – have some respect for it.
in other words – most abstractions are a front-end to operations on bytes. it’s ok to have various designs, but making lower layers inaccessible is just sad.
i say it’s the opoposite – it’s 2025, we should stop stroking the imaginaries of the 80s and return to the actual. just invest in making it as ergonomic and nimble as possible.
i find it hard understand why some programmers are so intent on hiding from the space they inhabit.
Pretending to control the situation feels better than embracing the chaos.
You are making a classic “sufficiently smart compiler” argument. These types of problems can’t be automagically solved without strong general AI inside the compiler. See also: SIMD, auto-parallelization, etc. We don’t have strong general AI, never mind inside the compiler.
Until we have such a compiler, you will be dependent on people caring a lot about physical data layout to make your software scalable and efficient.
I am not talking about low-level programming like Kernels or the latest-and-greatest video game, but I also refer to Office type applications, or other specialised software in other fields.
Seriously, think of those big and/or popular applications like Teams, Visual Studio, or some other application you use regularly. Programs like these SHOULD be made with some TLC to the point that SOME AREAS SHOULD CARE about the "mechanism that manipulates bytes in flat memory"
AOS to SOA is one of various things you can do to improve performance. Some popular applications may be doing that but I do question a number of them why it take over 10 seconds to launch... and that is on a good day.
It comes back full circle for the general "regulars" like me in the web/app world, creating something bespoke for customers or staff. Sure, we use an OOP/GC language but I push for clean, easy to read code. I like my code obvious to follow but when I see code that jumps in memory or takes a while to complete, I have to investigate WHY.
Each programming language can have their tips and tricks to follow and most of time the improvements can be handled in there. However, there are other times when I break down an object (a data structure) for better processing. It still falls back to understanding the basics of memory and processing.
In short this will ALWAYS be a thing even in 2025. Also, I would love to use Odin in the workplace and have "better control" - I have been using Odin for quite a few months on personal projects and I really like it. Just like the article, it is a language that has been made for me.
C doesn’t even care. You can cast an int to a function pointer if you want.
With Odin it’s taken me like 5 minutes including reading the section of the docs for the first time.
They can decay to function pointers (`$(unsafe)? $(extern)? fn($(inp),*) $(-> $(output))?`, example: unsafe extern fn() -> i32), which you can freely cast between function pointers, `*const/mut T` pointers or `usize`s. https://doc.rust-lang.org/reference/types/function-pointer.h...
I'm a game-play programmer and not really into memory management or complex math. I like things to be quick and easy to implement. My games are small. I have no need for custom allocators or SOA. All I want is a few thousand sprites at ~120fps. I normally just work in the browser with JS. I use Odin like it's a scripting language.
I really like the dumb stuff like... no semicolons at the end of lines, no parentheses around conditionals, the case statement doesn't need breaks, no need to write var or let, the basic iterators are nice. Having a built in vector 2 is really nice. Compiling my tiny programs is about as fast as refreshing a browser page.
I also really like C style procedural programing rather than object oriented code, but when you work in a language that most people use as OO, or the standard library is OO, your program will end up with mixed paradigms.
It's only been a few weeks, but I like Odin. It's like a statically typed and compiled scripting language.
Here's an example of how I use the nature and raylib bindings.
(Personally I have spent my weekend evaluating C-like languages and I need a break and to reset my palate for a bit)
https://nature-lang.org/docs/syntax
In the Type System section, a little text at the left margin is cut off for some lines.
So it strikes me that a new language may be the wrong approach to addressing C's issues. Can they truly not be addressed with C itself?
E.g., here's a list of some commonly mentioned issues:
* standard library is godawful, and composed almost entirely of foot guns. New languages fix this by providing new standard libraries. But that can be done just as well with C.
* lack of help with safety. The solutions people put forward generally involve some combination of static analysis disallowing potentially unsafe operations, runtime checks, and provided implementations of mechanisms around potentially unsafe operations (like allocators, and slices). Is there any reason these cannot be done with C (in fact, I know they all have been done).
* lack of various modern conveniences. I think there's two aspects of this. One is aesthetics -- people can feel that C code is inelegant or ugly. Since that's purely a matter of personal taste, we have to set that aside. The other is that C can often be pretty verbose. Although the syntax is terse, its low-level nature means that, in practice, you can end up writing a relatively large number of lines of code to do fairly simple things. C alternatives tend to provide syntax conveniences that streamline common & preferred patterns. But it strikes me that an advanced enough autocomplete would provide the same convenience (albeit without the terseness). We happen to have entered the age of advanced autocomplete.
Building a new language, along with the ecosystem to support it, is a lot of fun. But it also seems like a very inefficient way to address C's issues because you have to recreate so much (including all the things about C that aren't broken), and you have to reach some critical mass of adoption/usage to become relevant and sustainable. And to be frank, it's also a pretty ineffective way to address C's issues because it doesn't actually do anything to help all the existing C code. Very few projects are in a position to be rewritten. Much better would be to have a fine-grained set of solutions that code bases could adopt incrementally according to need and opportunity
Of course, I realize all this has been happening with C all along. I'm just pointing out that seems like the right approach, while these C alternatives, while fun and exciting (as far as these things go), they are probably just sound and fury that will ultimately fade away. (In fact, it might be worse if some catch on... C and all the C code bases will still be there, we'll just have more fragmentation.)
I made my own standard library to replace libc. The lack of safety is hard to do when you don't have a decent enough type system. C's lack of a proper array type is a good example of this.
Before making Odin, I tried making my own C compiler with some extensions, specifically adding proper arrays (slices) with bounds checking, and adding `defer`. This did help things a lot, but it wasn't enough. C still had fundamentally broken semantics in so many places that just "fixing" the problems of C in C was not enough.
I didn't want to make Odin initially, but it was the conclusion I had after trying to fix something that cannot be fixed.
It's a short game made in Odin, but I spent a lot of effort in polishing it so that it would be a pleasant little strange experience.
It was the first commercial game made in Odin.
Some games made by other people:
Solar Storm (turn-based artillery game): https://store.steampowered.com/app/2754920/Solar_Storm/
2deez (fighting game, not yet released): https://store.steampowered.com/app/3583000/2Deez/
This is (almost *) bounds safety with -fsanitize=bounds
*) with some pending some compiler improvements it will be perfect
(edit: updated godbolt link)
There seems to be some sort of force in the programming language landscape that prevents a language that is too similar to another language from being able to succeed. And I don't just mean something like "Python versus Ruby", although IMHO even that was a bit of a fluke due to geography, but the general inability to create a variant of C that everybody uses.
The other problem is you still end up pushed in the direction of a new language anyhow. Let's say you create C-New and it "fixes pointers" so now they're safe. I don't care how you do that. But obviously that involves some sort of writing into C-New new guarantees that pointers take. But if you're conceiving of this as "still basically C", such that you can just call into C code, when you pass your C-New pointer into C-Old, you can no longer make those guarantees. You still basically have to treat C-Old as a remote call, just like Python or Go or Lua, and put it at arm's length.
The extent to which you can "fix C" without creating this constraint is fairly limited. It's a very well defined language at this point with extremely strong opinions.
As for "C alternatives", actually, the era of C alternatives has passed. C++, Java, Objective-C, C#, many takes on the problem, none perhaps nailing the totality of the C problem space but the union of them all pretty much does. The era we have finally, at long last, it's about time we entered is the era of programming languages that aren't even reactions to C anymore, but are just their own thing.
The process of bringing up an ecosystem that isn't C is now well-trod. It's risky, certainly, but it's been done a dozen times over. It's often the only practical way forward.
And I know malloc/free is the allocator, but you cannot override it either.
TBH it was also news to me, I discovered it randomly while browsing vulnerabilities… Printf also allocates, and a ton of other stdlib functions as well.
I returned to the language after a stint of work in other tech and to my utter amazement, the parametric polymorphism that was added to the language felt “right” and did not ruin the comprehensibility of the core library.
Thank you gingerBill!
I am dropping the link here so for those who can, should donate, and even if you don't use it, you should consider supporting this and other similar endeavors so they can't stop the signal, and keep it going: https://github.com/sponsors/odin-lang
Like... how easy is it to not know how anything works and generate a "working" program, using the loosest possible definition of "working", using LLMS?
A lot of it just comes down to good taste; he's upfront about the language's influences. When something is good there's no shame in taking it. e.g. The standard library package structure is very similar to Go's.
There are plenty of innovations as well. I haven't seen anything quite like the context system before, definitely not done as well as in Odin.
Odin will be hopefully finally specced. Jai will be finally out in public in a stable version. And Zig will be still at 0.x, but usable.
Three will enter, but only one will be the victor.
There's room for all of them, each with its strengths and weaknesses.
That's the only way they're going to keep evolving.
The ultimate solution is a common base that allows interoperability of course.
Truth is Rust gets a lot of headlines especially in relation to "memory safety" being thrown around in recent years. Not saying it isn't something to be taken seriously but "Rust" and "Memory Safety" are interchangable when a non-programmer blogger writes an article.
- Its Rust that being added into the Linux Kernel. - Its Rust being added to Window Kernel - Its Rust building replacement software (might I add already battle tested projects!) such as coreutil + Expect there to be (if not already) rewrites of git, nginx, sqlite -- in memory-safe, blazingly fast Rust.
Rust, rust, rust, rust... RUST!
Truth is Rust is taking over. This will either become a HUGE waste of time or mistake.. or it will be the standard for low-level, memory safe software... cancelling out C or C++ originals that have existed for decades (and already stable)
The way things are going, you will have Rust... and in the distance (some further than others) is Go, Zig, Jai, Odin, C3, etc. Of course, language like Java, C#, Javascript, etc -- will continue as they are.
This is not a dig or hatred. This is just how I see it... and I am really liking Odin. I mean what jobs will I see advertised in the next 10-15 years? I bet I will see more and more Rust in the coming years. The others I mentioned above... I expect to see some Go and some odd Zig... but will I see an Odin or Jai?
I hope I am proven wrong. As I wrote, I like Odin!
Did I miss an announcement?
mrkeen•7mo ago
> This makes ZII extra powerful! There is little risk of variables accidentally being uninitialized.
The cure is worse than the problem. I don't want to 'safely' propagate my incorrect value throughout the program.
If we're in the business of making new languages, why not compile-time error for reading memory that hasn't been written? Even a runtime crash would be preferable.
ratatoskrt•7mo ago
so... like Rust?
Timwi•7mo ago
dontlaugh•7mo ago
neonsunset•7mo ago
dontlaugh•7mo ago
Without unsafe, zero init is not needed.
neonsunset•7mo ago
This is opposite to the way unsafe (either syntax or known unsafe APIs) is used today.
dontlaugh•7mo ago
All use of p/invoke is also unsafe though, even if the keyword isn’t used. And it’s much more common to wrap a C library than to write a buffer pool.
mrkeen•7mo ago
electroly•7mo ago
masfoobar•7mo ago
In C#, to have all zeroes, you would do "Guid.Empty"
masfoobar•7mo ago
"new Guid()" is equiverlant "Guid.Empty"
Personally, I would prefer writing "Guid.Empty" as it is cleaner. It also helps ensure you understand reading other developers code... their intensions.
Afterall, a lesser experienced developer may not know (or simply forgot) that "new Guid()" does not create a new, unique value. So writing "new Guid()" looks more misleading.
var myGuid1 = new Guid(); // all zeroes, or a value?
var myGuid2 = Guid.Empty; // Ah.. all zeroes.
It is ESPECIALLY cleaner when doing comparison :-
if(myGuid2 == Guid.Empty) { }
To set a Guid, you would do :-
myGuid2 = Guid.NewGuid(); // This makes sense.
neonsunset•7mo ago
If supplying Guid is optional, you just make it Guid?.
To be fair, I don't think offering default(T) by default (ha) is the best choice for structs. In F#, you have to explicitly do `Unchecked.defaultOf` and otherwise it will just not let you have your way - it is watertight. I much prefer this approach even if it can be less convenient at times.
munificent•7mo ago
Java has the same problem.
(Dart, which I work on, does not. In Dart, you really truly can't observe an instance field before it has been initialized.)
thasso•7mo ago
yusina•7mo ago
90s_dev•7mo ago
yusina•7mo ago
I "often enough" drive around with my car without crashing. But for the rare case that I might, I'm wearing a seatbelt and have an airbag. Instead of saying "well I better be careful" or running a static analyzer on my trip planning that guarantees I won't crash. We do that when lives are on the line, why not apply those lessons to other areas where people have been making the same mistakes for decades?
sph•7mo ago
It is impossible to post about a language on this forum before the pearl clutching starts if the compiler is a bit lenient instead of triple checking every single expression and making your sign a release of liability.
Sometimes, ergonomics and ease-of-programming win over extreme safety. You’ll find that billion dollar businesses have been built on zero-as-default (like in Go) and often people reaching for it or Go are just writing small personal apps, not cruise missile navigation system.
It gets really tiring.
/rant
yusina•7mo ago
Or are you saying that a certain level of bugs is fine and we are at that level? Are you fine with the quality of all the software out there? Then yes, this discussion is probably not for you.
sph•7mo ago
This is the kind of generalisation I'm ranting against.
It is not constructive to extrapolate any kind of discussion about a single, perhaps niche, programming languages with applicable advice for "all the software out there". But you probably knew that already.
vacuity•7mo ago
There is a lot of subpar software out there, and the rest is largely decent-but-not-great. If it's security I want, that's commonly lacking, and hugely so. If it's performance I want, that's commonly lacking[0]. If it's documentation...you get the idea. We should have rigor by default, and if that means software is produced slower, I frankly don't see the problem with that. (Although commercial viability has gone out the window unless big players comply.) Exceptions will be carved out depending on the scope of the program. It's much harder to add in rigor post hoc. The end goal is quality.
The other issue is that a program's scope is indeed broader than controlling lives, and yet there are many bad outcomes. If I just get my passwords stolen or my computer crashes daily or my messaging app takes a bit too long to load every time, what is the harm? Of course those are wildly different outcomes, but I think at least the first and second are obviously quality issues, and I think the third is also important. Why is the third important? When software is such an integral part of users' lives, minor issues cause faults that prompt workarounds or inefficiencies. [1] discusses a similar line of thought. I know I personally avoid doing some actions commonly (e.g. check LinkedIn) because they involve pain points around waiting for my browser to load and whatnot, nothing major but something that's always present. Software ("automation") in theory makes all things that the user implicitly desires to be non-pain points for the user. An interesting blend of issues is system dialog password prompts, which users will generally try to either avoid or address on autopilot, which tends to reduce security. Or take system update restarts, which induce not updating frequently. Or take what is perhaps my favorite invectives: blaming Electron apps. One Electron app can be inconvenient. Multiple Electron apps can be absurd. I feel like I shouldn't have to justify calling out Electron on HN, but I do, but I won't here. And take unintended uses: if I need to set down an injured person across two chairs, I sure hope a chair doesn't break or something. Sure, that's not the intended use case of a chair, but I don't think it's unreasonable that a well-made chair would not fail to live up to my expectations. I wouldn't put an elephant on the chair either way, because intuitively I don't expect that much. Even then, users may expect more out of software than is reasonable, but that should be remedied and not overlooked.
Do not mistake having users for having a quality product.
[0] https://news.ycombinator.com/item?id=43971464 [1] https://blog.regehr.org/archives/861
90s_dev•7mo ago
vacuity•7mo ago
And you seem to have ignored a lot of what I said, as if I was just talking about a few rare, critical problems.
[0] https://old.reddit.com/r/unpopularopinion/comments/d6g6qr/it...
90s_dev•7mo ago
Depends.
> Are ads fine if they can be used to track every detail of what you do and want?
Depends.
> Was it fine when CrowdStrike caused Windows systems in airports BSOD and lead to massive delays?
Depends.
That's my whole point. You're presuming some absolutes which aren't universally necessitated or desired.
vacuity•7mo ago
"It is difficult to get a man to understand something, when his salary depends on his not understanding it."
- Upton Sinclair
and note (because it is not included in the quote) that a literal salary is not necessary or sufficient. Really? Is this not just resigning to subpar software? My counterpoint is that what is popular is not necessarily what should be popular. And I think you're still tunnel-visioning for a specific thing to criticize. Do I have to keep giving examples until I find one you will deign to agree is a serious issue? Just as hasty generalization is harmful, so is hasty specialization. Perhaps you personally don't see a problem, but there can be many reasons for that.
90s_dev•7mo ago
It's getting from point A to point B with whatever works best given the circumstances after considering all the pros and cons. Sometimes that is garbage software. I mean I've even used _____ once or twice! [edit] redacted to not throw any software under the bus
vacuity•7mo ago
I agree with you on this, but on this forum that's full of people who write software, I'm skeptical that making better software isn't often (usually?) the better choice. But I understand that this is one of those "critical mass" things where a few people can't do nearly as much.
90s_dev•7mo ago
vacuity•7mo ago
90s_dev•7mo ago
johnnyjeans•7mo ago
layer8•7mo ago
If you have a sparse array of values (it might be structs), then you can use a zero value to mark an entry that isn’t currently in use, without the overhead of having to (re-)initialize the whole array up-front. In particular if it’s only one byte per array element that would need to be initialized as a marker, but the compiler would force you to initialize the complete array elements.
Similarly, there are often cases where a significant part of a struct typically remains set to its default values. If those are zero, which is commonly the case (or commonly can be made the case), then you can save a significant amount of extra write operations.
Furthermore, it also allows flexibility with algorithms that lazy-initialize the memory. An algorithm may be guaranteed to always end up initializing all of its memory, but the compiler would have no chance to determine this statically. So you’d have to perform a dummy initialization up-front just to silence the compiler.
bobbylarrybobby•7mo ago
jerf•7mo ago
To many people reading this this may be a "duh" but I find it is worth pointing out, because there are still some programmers who believe that C is somehow the "default" or "real" language of a computer and that everything about C is true of other languages, but that is not the case. Undefined behavior in C is undefined in C, specifically. Try to avoid taking ideas about UB out of C, and to the extent that they are related (which slowly but surely decreases over time), C++. It's the language, not the hardware that is defining UB.
gingerBill•7mo ago
C is just one language of many and you do not have to define the rules of a new language to it.
yusina•7mo ago
Let me explain. Conceptually, a pointer is an optional reference. Either it's nil or it references some object. (That reference may be valid or not, but that's a separate topic.) Optionals have been well understood in the programming language community for a long time. Many modern languages get them right, usually with some form of what Haskell calls the Maybe monad. May sound intimidating, but the gist is that in order to unwrap the thing inside, you pattern match on its structure. It's either Nothing or it's a value. You can't even syntactically talk about the wrapped thing outside this pattern matching. With such a construction, "dereferencing nil" is not a thing. You either have a pointer (optional reference) which needs the unwrapping, or you already did unwrap, then you have a non-null-pointer (non-optional reference). So, a language can easily encode all this in its type system without runtime overhead.
When inventing a new language that fixes flaws of C, why not fix this one as well?
tlb•7mo ago
thasso•7mo ago
This is not the whole story. You're making it sound like uninitialized variables _have_ a value but you can't be sure which one. This is not the case. Uninitialized variables don't have a value at all! [1] has a good example that shows how the intuition of "has a value but we don't know which" is wrong:
If you assume an uninitialized variable has a value (but you don't know which) this program should run to completion without issue. But this is not the case. From the compiler's point of view, x doesn't have a value at all and so it may choose to unconditionally return false. This is weird but it's the way things are.It's a Rust example but the same can happen in C/C++. In [2], the compiler turned a sanitization routine in Chromium into a no-op because they had accidentally introduced UB.
[1]: https://www.ralfj.de/blog/2019/07/14/uninit.html
[2]: https://issuetracker.google.com/issues/42402087?pli=1
gingerBill•7mo ago
Because that's a valid conceptualization you could have for a specific language. Your approach and the other person's approach are both valid but different, and as I said in another comment, they come with different compromises.
If you are thinking like some C programmers, then `int x;` can either have a value which is just not known at compile time, or you can think of it having a specialized value of "undefined". The compiler could work with either definition, it just happens that most compilers nowadays do for C and Rust at least use the definition you speak of, for better or for worse.
nlitened•7mo ago
I am pretty sure that in C, when a program reads uninitialized variable, it is an "undefined behavior", and it is pretty much allowed to be expected to crash — for example, if the variable turned out to be on an unallocated page of stack memory.
So literally the variable does not have a value at all, as that part of address space is not mapped to physical memory.
gingerBill•7mo ago
However, I was using that "C programmers" bit to explain the conceptualization aspect, and how it also applies to other languages. Not every language, even systems languages, have the same concepts as C, especially the same construction as "UB".
shwouchk•7mo ago
uecker•7mo ago
steveklabnik•7mo ago
maccard•7mo ago
> So literally the variable does not have a value at all, as that part of address space is not mapped to physical memory.
There are vanishingly few platforms where the stack you have in a C program maps to physical memory (even if you consider pages from the OS)
chipsrafferty•7mo ago
gingerBill•7mo ago
iainmerrick•7mo ago
I want to push back on the idea that it's a "trade-off", though -- what are the actual advantages of the ZII approach?
If it's just more convenient because you don't have to initialize everything manually, you can get that with the strict approach too, as it's easy to opt-in to the ZII style by giving your types default initializers. But importantly, the strict approach will catch cases where there isn't a sensible default and force you to fix them.
Is it runtime efficiency? It seems to me (but maybe not to everyone) that initialization time is unlikely to be significant, and if you make the ZII style opt-in, you can still get efficiency savings when you really need them.
The explicit initialization approach seems strictly better to me.
gingerBill•7mo ago
The thing is, initialization cost is a lot more than you think it is, especially when it's done on a per-object level rather than a "group" level.
This is kind of the point of trying to make the zero value useful, it's trivially initialized. And in languages that are much more strict in their approach, it is done at that per-object level which means instead of the cost of initialization being anywhere from free (VirtualAlloc/mmap has to produce zeroed memory) to trivially-linear (e.g. memset), to being a lot more nested hierarchies of initialization (e.g. for-loop with constructor for each value).
It's non-obvious why the "strict approach" would be worse, but it's more about how people actually program rather than a hypothetical approach to things.
So of course each style is about trade-offs. There are no solutions, only trade-offs. And different styles will have different trade-offs, even if they are not immediately obvious and require a bit of experience.
A good little video on this is from Casey Muratori, "Smart-Pointers, RAII, ZII? Becoming an N+2 programmer": https://www.youtube.com/watch?v=xt1KNDmOYqA
iainmerrick•7mo ago
But still, in other parts of the program, ZII is bad! That local or global variable pointing at an ArrayBuffer should definitely not be zero-initialized. Who wants a null pointer, or a pointer to random memory of unknown size? Much better to ensure that a) you actually construct a new TypedArray, and b) you don't use it until it's constructed.
I guess if you see the vast majority of your action happening inside big arrays of structs, pervasive ZII might make sense. But I see most of the action happening in local and temporary variables, where ZII is bad and explicit initialization is what you want.
Moving from JavaScript to TypeScript, to some extent you can get the best of both worlds. TS will do a very good (though not perfect) job of forcing you to initialize everything correctly, but you can still use TypedArray and DataView and take advantage of zero-initialization when you want to.
ZII for local variables reminds me of the SmallTalk / Obj-C thing where you could send messages to nil and they're silently ignored. I don't really know SmallTalk, but in Obj-C, to the best of my knowledge most serious programmers think messages to nil are a bad idea and a source of bugs.
Maybe this is another aspect where the games programming mindset is skewing things (besides the emphasis on low-level performance). In games, avoiding crashes is super important and you're probably willing to compromise on correctness in some cases. In most non-games applications, correctness is super important, and crashing early if something goes wrong is actually preferable.
gingerBill•7mo ago
I normally say "try to make the zero value useful" and not "ZII" (which was a mostly jokey term Casey Muratori came up with to reflect against RAII) because then it is clear that there are cases when it is not possible to do ZII. ZII is NOT a _maxim_ but what you should default to and then do something else where necessary. This is my point, and I can probably tell you even more examples of where "ZII is bad" than you could think of, but this is what is a problem describing the problem to people: they take it as a maxim not a default.
And regarding pointers, I'm in the camp that nil-pointers are the most trivial type of invalid pointer to catch empirically speaking. Yes they cause problems, but because how modern systems are structured with virtual memory, they are empirically trivial to catch and deal with. Yes you could design the type system of a language to make nil-pointers not be a thing unless you explicit opt into them, but then that has another trade-off which may or may not be a good thing depending on the application.
The Objective-C thing is just a poorly implemented system for handling `nil`. It should have been more consistent but wasn't. That's it.
I'd argue "correctness" is important in games too, but the conception of "correctness" is very different there. It's not about provability but testability, which are both valid forms of "correctness" but very different.
And in some non-game applications, crashing early is also a very bad thing, and for some games, crashing early is desired over corrupted saves or other things. It's all about which trade-offs you can afford, and I would not try to generalize too much.
iainmerrick•7mo ago
I don't think I'll ever abandon the idea that making code "correct by construction" is a good goal. It might not always be achievable or practical but I strongly feel it's always something to aim for. For me, silent zero initialization compromises that because there isn't always a safe default.
I think nil pointers are like NaNs in arithmetic. When a nil or a NaN crops up, it's too late to do anything useful with it, you generally have to work backwards in the debugger to figure out where the real problem started. I'd much rather be notified of problems immediately, and if that's at compile time, even better.
In the real world, sure, I don't code review every single arithmetic operation to see if it might overflow or divide by zero. But when the compiler can spot potential problem areas and force me to check them, that's really useful.
Rusky•7mo ago
gingerBill•7mo ago
Rusky•7mo ago
fc417fc802•7mo ago
Is there an option to leave something uninitialized? I often find the allocation of explicitly uninitialized objects to be a performance necessity in tight loops when I'm working with numpy.
O-stevns•7mo ago
x: int // initialized with its zero value
y: int = --- // uses uninitialized memory
igouy•7mo ago
Messages sent to the Smalltalk UndefinedObject instance are not silently ignored — #doesNotUnderstand.
Sometimes that run time message lookup has been used to extend behavior —
1986 "Encapsulators: A New Software Paradigm in Smalltalk-80"
https://dl.acm.org/doi/pdf/10.1145/28697.28731
igouy•7mo ago
elcritch•7mo ago
Yet, after a decade of embracing Swift that tries to eliminate that aspect of nil handling in obj-c, Apple software is buggier than it's ever been. Perhaps not crashing on every nil in large complex systems does lend itself to more stable system.
slowmovintarget•7mo ago
Much better outcomes and failure modes than RAII. IIRC, Odin mentions game programming as one of its use cases.
CyberDildonics•7mo ago
He thinks that every RAII variable is a failure point and that you only have to think about ownership if you are using RAII, so it incurs mental overhead.
The reality is that you have to understand the lifetime and ownership of your allocations no matter what. If the language does nothing for you the allocation will still have a lifetime and a place where the memory is deallocated.
He also talks about combining multiple allocations in to a single allocation that then gets split into multiple pointers, but that could easily be done in C++.
adamrezich•7mo ago
But this is explicitly the opposite of how the language is designed to be used, because that's the whole point of RAII.
CyberDildonics•7mo ago
Says who? If they all have the same lifetime multiple allocations can be combined into one allocation and deallocation. I've done it before, it makes perfect sense, although I would say it is a somewhat niche optimization.
adamrezich•7mo ago
There are other ways of writing code—which other languages can incentivize by being designed differently—where it is not a niche optimization, but rather the default.
CyberDildonics•7mo ago
Heap allocations can be done about 10 million times a second on one core, once you get them out of your loops they should make almost no impact, so grouping a few together when they are only being done once is rare and probably indicative of painting yourself into a corner where something inside a call stack inside hot loop still has to heap allocate.
The other reason is that most heap allocation is for lots of memory that will hold the same type. Manually grouping a bunch of allocations implies that you have multiple different types of arrays all buried inside a hot loop to the extent that you are trying to shave off whatever you can but can't lift those allocations out of the loop.
It basically implies that the optimization has to come without too many changes, because if you could do whatever you want they wouldn't be in the loop in the first place. For these reasons it is a half measure in a niche scenario.
There are other ways of writing code—which other languages can incentivize by being designed differently—where it is not a niche optimization, but rather the default.
This is completely abstract, can you show me exactly what you mean and why it can't be done easily in C++ or with RAII ? It sounds like rationalizing a situation you don't want to be in in the first place.
lerno•7mo ago
Clearly the lack of zeroing in C was a trade-off at the time. Just like UB on signed overflow. And now people seem to consider them "obvious correct designs".
Tuna-Fish•7mo ago
"Improperly using a variable before it is initialized" is a very common class of bug, and an easy programming error to make. Zero-initializing everything does not solve it! It just converts the bugs from ones where random stack frame trash is used in lieu of the proper value into ones where zeroes are used. If you wanted a zero value, it's fine, but quite possibly you wanted something else instead and missed it because of complex initialization logic or something.
What I want is a compiler that slaps me when I forget to initialize a proper value, not one that quietly picks a magic value it thinks I might have meant.
nickpsecurity•7mo ago
Tuna-Fish•7mo ago
It's just that "all values are defined to be zero-initialized, and you can use them as such" is a horrible decision. It means that you cannot even get best effort warnings for lack of initialization, because as far as the compiler knows you might have meant to use the zero value.
fc417fc802•7mo ago
fc417fc802•7mo ago
When I make a logic error the absolute ideal situation is that the compiler rejects the program. The higher the frequency of that result the better.
nickpsecurity•7mo ago
I believed the claim was about whether a compiler would initialize memory to zero. Whatever I said would be done by the compiler.
fc417fc802•7mo ago
UB plus a memory access sanitizer automates the practice that you described.
fc417fc802•7mo ago
And remains so. When number crunching it is often beneficial to be able to explicitly opt out of initialization for performance reasons.
dooglius•7mo ago
https://en.wikipedia.org/wiki/Rice%27s_theorem?useskin=vecto...
TheCoelacanth•7mo ago
dooglius•7mo ago
jerf•7mo ago
This isn't some whacko far out idea. Most languages already today don't have any way (modulo "unsafe", or some super-carefully declared and defined method that is not the normal operation of the language) of reading uninitialized memory. It's only the residual C-likes bringing up the rear where this is even a question.
(I wouldn't count Odin's "explicitly label this as not getting initialized"; I'm talking about defaults being sharp and pointy. If a programmer explicitly asks for the sharp and pointy, then it's a valid choice to give it to them.)
dooglius•7mo ago
reverius42•7mo ago
trealira•7mo ago
trealira•7mo ago
But it feels like it would be hard for something that isn't a formal proof assistant to prove this. The equivalent Rust code (unidiomatic as it would be to roll your own linked list code) would require you to set "new_list" to be None before the loop starts.
steveklabnik•7mo ago
trealira•7mo ago
steveklabnik•7mo ago
Furthermore, language designers are also human. Many of the languages used in industry weren’t even created by people who have a PLT background, or were created a very long time ago.
trealira•7mo ago
On the other hand, in languages where you don't have to explicitly initalize variables after declaring them, I think the best solution is to do flow analysis and forbid uses of uninitialized variables rather than implicitly set them all to null or zero. The latter solution just leads to confusing bugs if you ever don't initialize a variable in all branches (which is why I think that should be a compiler error). It's only a little better than C's solution of just letting the value be undefined, or in practice, a non-deterministic garbage value. An error message just seems more user-friendly, particularly for beginner programmers who are more likely to make mistakes like those and not immediately understand what the problem is.
I do get what you're saying about it being simpler, but I don't think it's conceptually simpler for the user to always initialize variables to zero or null.
steveklabnik•7mo ago
I agree, for sure.
> I don't think it's conceptually simpler for the user to always initialize variables to zero or null.
I'm not saying zero, I'm just saying null. Because null is a valid value for that type, it's equivalent to
> Not having uninitialized variables by requiring the user to provide some value
trealira•7mo ago
What would happent to the primitive types of that language, then? When I said that, I was thinking of languages like Go and Java, both of which implicitly initialize integers to 0, booleans to false, floats to 0.0, and references to nil or null. That's what I'm calling less user-friendly than disallowing variables that aren't explicitly initialized before being used in all branches.
steveklabnik•7mo ago
Not every language has them. A common example here is JavaScript, which, while `undefined` is a thing in, is closer to null than the C sense of undefined.
> When I said that, I was thinking of languages like Go and Java, both of which implicitly initialize integers to 0, booleans to false, floats to 0.0, and references to nil or null.
Right, that's zero initialization. I also agree that I think it's a mis-feature generally, but I think in the overall design space there's reasons you'd end up there. It's not the path I'd personally take, but I can see why people choose it, even if I disagree.
drannex•7mo ago
lblume•7mo ago
melodyogonna•7mo ago
variadix•7mo ago
jimbob45•7mo ago
variadix•7mo ago
Hopefully I’m communicating why it is useful to leave the default behavior undefined or invalid. It doesn’t really have to have anything to do with performance, signed wrapping is no less performant on two’s-complement machines (barring compiler optimizations enabled by assuming no overflow) since it is the result produced by add instructions on overflow. The benefit is that it enables instrumentation and analyzers to detect this behavior because it is known to be invalid and not something the programmer intended.
As an analogy, consider what would happen if you defined out of bounds array access to be _something_, now analyzers and instrumentation cannot detect this as an error, since the programmer may have intended for whatever that defined result is to occur in that case.
canucker2016•7mo ago
see https://msrc.microsoft.com/blog/2020/05/solving-uninitialize...
Initializing the stack var to zero would have helped mitigate the recently discovered problem in GTA San Andreas (the real problem is an unvalidated data file) - see https://cookieplmonster.github.io/2025/04/23/gta-san-andreas...
Arnavion•7mo ago
Read the comment you responded to again, carefully. It's not presenting the dichotomy you think it is.
the__alchemist•7mo ago