HNHacker News
TopNewBestAskShowJobs

lerno

1,425 karma · joined April 1, 2014

submissionscomments
lerno··on Unsigned sizes: A five year mistake
The difference is that in the unsigned case you get a seemingly plausible value, and in the signed case you get a negative value which you can be sure is wrong. This is the problem.
lerno··on Zig 0.16.0 Release Notes
Odin started in 2017, had a fraction of the contributors and is soon 1.0, so that explanation isn’t cutting it.

If you instead look at the over 500 kloc of the Zig source compared to the 70 kloc of the Odin one, it’s a bit clearer why the delay happened: the goals of Zig kept expanding.

But not only that: ”juicy main” and Io is something that could have been in Zig from the early days and yet it isn’t. In the Io case it’s Zig pivoting from ”we have colorless async!” to no async, to Io.

In other words, Andrew is still experimenting with the language (and more is to come, like ranged integers). This is not the signs of a maturing language, it a language still very much in flux, trying to find its form.

The contrast to Odin is that for the last 2-3 years it has had minimal syntax tweaks, and is essentially in release candidate mode for the language.

Even if Zig didn’t need more changes, it would still need that stabilization period.

This tells us Zig is still rather far from 1.0.

I wonder what Andrew is thinking about all this.

lerno··on Zig 0.16.0 Release Notes
I think Andrew should seriously think about decoupling the toolchain from the language to put focus on reaching stability there. As it is, we have the compiler with surrounding toolchain, the standard library and the language. The two former can keep evolving even language is fixed.

Just being ambitious isn’t necessarily good. Look at Perl6.

lerno··on C3 0.7.11: closing out the 0.7 series before 0.8
C3 0.7.11 is the final release in the 0.7 series.

This version focuses on tightening semantics, improving type inference, and cleaning up edge cases rather than adding new features. Some parts of the standard library (like matrices) have also been reworked.

The goal is to stabilize behavior and reduce inconsistencies before starting the 0.8 cycle.

C3 is aiming to stay close to C in terms of control and predictability, while reducing complexity in everyday use.

lerno··on Type resolution redesign, with language changes to taste
I just have to add another reflection:

One thing that tends to be overlooked when discussing changes is the ecosystem effect of frequent changes.

A language that breaks frequently doesn't just impose upgrade work on apps, but also discourages the creation of long-lived libraries and tools. Anything that sits between the language and the user (linters, bindings, frameworks, teaching material, tutorials etc) has to to some degree "chase the language"

This means that the ecosystem will skew toward very actively maintained libraries and away from "write once then leave it alone" libs. And this the trade-off is reasonable during early language design, but it's worth acknowledging that it has real consequences for ecosystem growth.

One should note that other newer languages have put significant effort into minimizing this churn, precisely to allow the latter type of ecosystem to also form. So it's kind of an experiment, and it will be interesting to see which approach ends up producing the larger ecosystem over time.

lerno··on Type resolution redesign, with language changes to taste
> Not at all, if the team needs 30 more years they should take it.

Yes, I understand that is the opinion in the Zig community. As an outsider, it seems odd to me to pick a language that I constantly need to maintain.

>> However, the outlook for a Zig 1.0 is bleak from what I gather from Zig social forums: the most optimistic estimate I’ve heard is 2029 for 1.0.

> Funny you see it as bleak when most of the community sees it as the most excitinh thing in systems programming happening right now.

You misread that one. I was talking about the odds of seeing a 1.0 version of Zig soon.

> I think you comment is in bad faith, all the big zig projects say that the upgrade path is never a main concern, just read HN comments here or on other zig threads, people ask about this a lot and maintains always answer.

Maybe you didn't read what I wrote carefully enough. This is part of the protectiveness from the Zig community that prompted me to write in the first place.

WITHIN the Zig community it is deemed acceptable for Zig upgrades to break code. Consequently it becomes simple survivor bias that people who use Zig for larger projects don't think that this is a major concern BECAUSE IF THEY FELT IT WAS A CONCERN THEY WOULD NOT USE ZIG.

Whether programmers at large feel that this is a problem is an unknown still, since Zig has not yet reached to point of general adoption (when people use Zig because they have to, rather than because they want to).

However, it is INCORRECT to state that just because a language is not yet 1.0 it needs to break older code aggressively without deprecation paths. As an example, Odin removed the old `os` module and replaced it with the new "os2". This break was announced half a year in advance and lots of thought was put into reducing work for developers: https://odin-lang.org/news/moving-towards-a-new-core-os/

In the case of C3, breaking changes only happen once a year with stdlib going through the general process of deprecating functions long before removing them.

I wanted to highlight how these are quite different approaches. For established languages, this is of course even more rigorous, but neither C3 nor Odin are 1.0, and still see this as valuable and their communities then end up expecting it.

So please understand that when you say "it's never a main[sic] concern", this is simple survivor bias.

lerno··on Zig – Type Resolution Redesign and Language Changes
I am using games, because they pop up more often (gamejams etc), but we can see the same if we'd look at utility apps. Do you want to broaden that to "Odin is more explicitly marked towards writing applications", but if so what would that say of Zig?
lerno··on Type resolution redesign, with language changes to taste
> However we value explicitness and allow the developer to do anything they need to do*

* except for having unused variables. Those are so dangerous the compiler will refuse the code every time.

lerno··on Type resolution redesign, with language changes to taste
It’s good to see that this is finally addressed. It’s been a well known broken part of the language semantics for years.

There are similar hidden quirks in the language that will need to be addressed at some point, such as integer promotion semantics.

To address the question about stability: the Zig community are already used to Zig breaking between 0.x versions. Unlike competitors such as Odin or my own C3, there is no expectation that Zig is trying to minimize upgrading problems.

This is a cultural thing, it would be no real problem to be clear about deprecations, but in the Zig community it’s simply not valued. In fact it’s a source of pride to be able to adapt as fast as possible to the new changes.

I like to talk about expectation management, and this is a great example of it.

In discussions, it is often falsely argued that ”Zig is not 1.0 so breaks are expected” in order to motivate the frequent breaks. However, there are degrees to how you handle breaks, and Zig is clearly in the ”we don’t care to reduce the work”-camp.

If someone is trying to get a more objective look at the Zig upgrade path, then it’s worth keeping in mind that the tradition in Zig is to offload all the work on the user.

The argument, which is frequently voiced, is that ”breaking things will make the language get better and so it’s good that there are language breaks”

It is certainly true that breaking changes are needed, but most people outside of the Zig community would expect it to be done with more care (deprecation paths etc)

Secondly, it should perhaps be a concern for Zig, now at 10 years old, to still produce solidly breaking code every half year.

10 years is the common point where languages go 1.0. However, the outlook for a Zig 1.0 is bleak from what I gather from Zig social forums: the most optimistic estimate I’ve heard is 2029 for 1.0.

This means that in the future, projects using Zig can still expect any libraries and applications to bitrot quickly if they are not constantly maintained.

Putting this in contrast with Odin (9 years old) which is essentially 1.0 already and has been stable for several years.

Maybe this also explains the difference in actual output. For example the number of games I know of written in Odin is somewhere between 5 to 10 times as many as Zig games. Now weighing in that Zig has maybe 5 or 10 times as many users, it means Odin users are somewhere between 20-100 times as likely to have written a playable game.

There are several explanations as to why this is: we could discuss whether the availability of SDL, Raylib etc is easier on Odin (then why is Zig less friendly?), maybe more Odin has better programmers (then why do better programmers choose Odin over Zig), maybe it’s just easier to write resource intensive applications with Odin than Zig (then what do we make of Zig’s claim of optimality?)

If we look past the excuses made for Zig (”it’s easy to fix breaks” ”it’s not 1.0”) and the hype (”Zig is much safer than C” ”Zig makes me so productive”) and compare with Odin in actual productivity, stability and compilation speed (neither C3 nor Odin requires 100s of GB of cache to compile in less than a second using LLVM) then Zig is not looking particularly good.

Even things like build.zig, often touted as a great thing, is making it really hard for a Zig beginner (”to build your first Hello World, first understand this build script in non-trivial Zig”). Then for IDEs, suddenly something like just reading the configuration of what is going to be used for building is hidden behind an opaque Zig script. These trade-offs are rarely talked about, as both criticism and hype is usually based on surface rather than depth.

Well, that’s long enough of a comment.

To round it off I’d like to end on a positive note: I find the Zig community nice and welcoming. So if you’re trying Zig out (and better do that, don’t let others’ opinions - including mine - prevent you from trying things out) do so.

If you want to evaluate Zig against competitors, I’d recommend comparing it to D, Odin, Jai and C3.

lerno··on C3 0.7.10: constdef Takes Shape
C3 0.7.10 introduces constdef, syntactically and semantically making a clear distinction between proper enums and "enums is collection of constants".

Other improvements in this release:

- Much improved MSVC cross compilation

- Quality-of-life fixes

- Custom LLVM builds to reduce external dependencies

lerno··on C3 0.7.9 with Updated Generics
C3 is a language designed as an evolution of C, without retaining strict backwards compatibility, but excellent interop with C.

This version departs from its rather uncommon module-based generics, but doesn't go all the way to C++ style templates. Instead generics can be grouped for common constraint checking on template parameters and instantiation. As usual it also contains fixes and additions to the stdlib.

Some older posts on C3:

- https://news.ycombinator.com/item?id=46463921

- https://news.ycombinator.com/item?id=43569724

- https://news.ycombinator.com/item?id=24108980

- https://news.ycombinator.com/item?id=27876570

- https://news.ycombinator.com/item?id=32005678

Try out C3 in the browser:

- https://www.learn-c3.org

Here are some interviews on C3:

- https://www.youtube.com/watch?v=UC8VDRJqXfc

- https://www.youtube.com/watch?v=9rS8MVZH-vA

Here is a series doing various tasks in C3, slightly dated:

- https://ebn.codeberg.page/programming/c3/c3-file-io/

Repository with link to various C3 resources and projects:

- https://github.com/c3lang/c3-showcase

Some projects:

- Gameboy emulator https://github.com/OdnetninI/Gameboy-Emulator/

- RISCV Bare metal Hello World: https://www.youtube.com/watch?v=0iAJxx6Ok4E

- "Depths of Daemonheim" roguelike https://github.com/TechnicalFowl/7DRL-2025

lerno··on Zen-C: Write like a high-level language, run like C
I didn't see any similarities to C3, quite the opposite.
lerno··on Zen-C: Write like a high-level language, run like C
C2 (http://c2lang.org) similarly compiles to C, but arguably more readable C code from what I can see. The benefits are (1) easy access to pretty much any platform with little extra work (2) significantly less long term work compared to integrating with LLVM or similar (3) if it's readable enough, it might be submitted as "C code" in working environments which mandate C.
lerno··on Zen-C: Write like a high-level language, run like C
It would be interesting to hear the motivation for it.
lerno··on The C3 Programming Language
But it of course move semantics and destructors affect all things. If the goal is to call and be callable from C without special constructs, how would you make the C code respect the move semantics and destructors?
lerno··on The C3 Programming Language
They have different wins. I think labelled break/continue help because they are clearer in locally expressing what the point is. If you see `goto NEXT;` you can kind of guess the intention, but `continue OUTER;` doesn't require you to read the code at the label and check what's happening there. `defer catch` and `defer try` helps avoiding some booleans otherwise needed with just a basic defer. `defer` on its own otherwise sometimes needs booleans to track what should be closed. With goto those naturally go to different cleanup sections.

I keep revisiting goto though. I like it a lot for its simplicity.

lerno··on The C3 Programming Language
There is https://github.com/c3lang/c3c/issues/829
lerno··on The C3 Programming Language
That's the kind of thing I was thinking about. You can solve that with a switch in C3, but it's not as nice. However, this accounts for no more than 1% of all my goto uses (from a quick inspection), which is too little to build a feature from (discipline is needed to prevent a language from ballooning, it's hard to say no). I am looking for some use for it that can redeem its inclusion.
lerno··on The C3 Programming Language
I don't recall exactly, I think we rehashed the same points.
lerno··on The C3 Programming Language
Please consider a variable `List{int}[3] x`, this is an array of 3 List{int} containing List{int}. If we do `x[1]` we will get an element of List{int}, from the middle element in the array. If we then further index this with [5], like `x[1][5]` we will get the 5th element of that list.

If we look at `int*`, the dereference will peel off the `*` resulting in `int`.

So, the way C3 types are declared is the most inside one is to the left, the outermost to the right. Indexing or dereferencing will peel off the rightmost part.

C uses a different way to do this, we place `*` and `[]` not on the type but on the variable, in the order it must be unpacked. So given `int (*foo) x[4]` we first dereference it (from inside) int[4], then index from the right.

If we wanted to extract a standalone type from this, we'd have `int(*)[4]` for a pointer to an array of 4 integers. For "left is innermost", the declaration would instead be `int[4]*`. If left-is-innermost we can easily describe a pointer to an array of int pointers (which happens in C3 since arrays don't implicitly decay) int*[4]*. In C that be "int*(*)[4]", which is generally regarded as less easy to read, not the least because you need to think of which of * or [] has priority.

That said, I do think that C has a really nice ordering to subscripts, but it was unfortunately not possible to retain it.

lerno··on The C3 Programming Language
Let's say you have find foo::bar(), then we know that the path is <some path>::foo, the function is `bar` consequently we search for all modules matching the substring ::foo, and depending on whether (1) we get multiple matches (2) we only get a match that is not properly visible (3) we get a match that isn't imported, (4) we get no match or (5) we get a visible match, we print different things. In the case 1-4, we give good errors to allow the user to take the proper action.

If instead we had foo.bar(), we cannot know if this is the method "bar" on local or global foo, or a function "bar()" in a path matching the substring "foo". Consequently we cannot properly issue 4, since we don't know what the intent was.

So far, not so bad. Let's say it's instead foo::baz::bar(). In the :: case, we don't have any change in complexity, we simply match ::foo::baz instead.

However, for foo.baz.bar(), we get more cases, and let us also bring in the possibility of a function pointer being invoked: 1. It is invoking the method bar() on the global baz is a module that ends with "foo" 2. It is calling a function pointer stored in member bar on the global variable baz is a module that ends with "foo" 3. It is calling the function bar() in a module that ends with "foo.baz" 4. It is calling the function pointer stored in the global bar in a module that ends with "foo.baz" 5. It is invoking the method bar on the member baz of the local foo 6. It is calling a function pointer stored in the member bar in the member baz of the local foo

This might seem doable, but note that for every module we have that has a struct, we need to speculatively dive into it to see if it might give a match. And then give a good error message to the user if everything fails.

Note here that if we had yet another level, `foo.bar.baz.abc()` then the number of combinations to search increases yet again.

lerno··on The C3 Programming Language
Of course you can explicitly shorten paths. I was talking about C3's path shortening which is doing this for you. This means you do not need to alias imports, which is otherwise how languages do it.

I don't want to get too far into details, but it's understandable that people misunderstand it if they haven't used it, as it's a novel approach not used by any other language.

lerno··on The C3 Programming Language
Two reasons, the second being the important: (1) If I read "io.print", is this "the print function in the module io" or "the print method for the variable io". There tends to be an overlap in naming here so that's a downside (2) parsing and semantic checking is much easier if the namespace is clear from the grammar.

In particular, C3's "path shortening", where you're allowed to write `file::open("foo.txt")` rather than having to use the full `std::io::file::open("foo.txt")` is only made possible because the namespace is distinct at the grammar level.

If we play with changing the syntax because it isn't as elegant as `file.open("foo.txt")`, we'd have to pay by actually writing `std.io.file.open("foo.txt")` or change to a flat module system. That is a fairly steep semantic cost to pay for a nicer namespace separator.

I might have overlooked some options, if so - let me know.

lerno··on The C3 Programming Language
It does compile to WASM.
lerno··on The C3 Programming Language
In C3 it's complicated. On one hand the lack of goto means defers are more straightforward, but the biggest problem is that once you have a different way to handle cleanup and you have labelled break/continue, and you have the nextcase to jump to arbitrary cases, there's very little left for goto to do.

It is limited to when you want to jump from within an if statement out across some statements and run the remaining code. It saves one level of indentation, but being so rare, it's hard to justify the complexity.

I keep going back and see if I find some usecase that could motivate putting goto back in but it so far nothing. The "nextcase" allows C3 to express arbitrary jumps back and forth, although not as straightforward as goto.

lerno··on The C3 Programming Language
Still looking for a good design: https://github.com/c3lang/c3c/issues/829
lerno··on The C3 Programming Language
Maybe these two could be interesting?

https://lowbytefox.dev/blog/from-zig-to-c3/

https://alloc.dev/2025/05/29/learning_c3

lerno··on The C3 Programming Language
The difference from an assert is that for "require" they are compiled into the caller frame, so things like stack traces (which is available in safe mode) will point exactly to where the violation happened.

Because of inlining them at the call site happens, static analysis will already pick up some obvious violations.

Finally, these contracts may be used to compile time check otherwise untyped arguments to macros.

lerno··on The C3 Programming Language
Maybe also worth mentioning is that some static analysis is done using these contracts as well. With more coming.
lerno··on C3 vs. Zig in 2025: Who's Fixing C? [video]
This one could perhaps have been a little more in-depth. Still, I shouldn't complain.
← PreviousPage 2 of 13Next →