What Zig felt like, coming from Rust

164 pointsposted 8 hours ago
by ksec

195 Comments

csense

6 hours ago

One of the section headings says "Mutation vs. immutable monad is the core difference" but this is not true.

The code in that section is clear in its purpose and broad outline: "Give me a function and data; if the data is bare apply the function to it; if the data is a container apply the function to each item inside it."

You can absolutely do that with immutable data structures in Zig. You just have to pass an allocator to the function (i.e. instead of calling data.flat_map(f), you call data.flat_map(a, f) where a is your allocator).

That the Zig version of the code does mutation is a matter of programmer choice, not something imposed by the language.

(Also, what do monads have to do with it?)

tialaramex

an hour ago

So, what you're not seeing is that even though the Rust doesn't announce mutation the implementation may be mutation anyway if that's probably faster/ cheaper.

    unsafe impl<I, U, F> InPlaceIterable for FlatMap<I, U, F> where
    I: InPlaceIterable,
    U: BoundedSize + IntoIterator,
What you're seeing is the graceful goose on the water moving forward at pace. Beneath there is frantic action to make that happen. The Rust surface was more a maintainable immutable operations, but the implementation is a frantic whirling mutation like the Zig.

In Zig you end up spending a lot of time writing How to do a thing, where in Rust you only wrote What the thing is and the machine did it. There are edge cases where Zig's explicitness wins for the best programmers, but there just aren't enough of those cases or those programmers for this to net out IMNSHO.

slopinthebag

44 minutes ago

yes people don't understand that there is often a runtime performance cost to explictness as well

bunderbunder

6 hours ago

Further up he discusses that using a functional paradigm is possible, but concludes that it doesn’t feel like a practical choice because of how Zig does memory management:

> But the language quickly forces you to diverge from the functional style, mostly because you’re now dealing with allocators directly, and a genuinely pure functional approach means constantly constructing new structures. That’s either expensive in memory or expensive in the manual bookkeeping needed to avoid it.

So I don’t think he’s trying to say that it’s literally impossible. It felt more like the result of a good faith attempt to understand how Zig itself actually wants to be used, and to compare that to how he’s used to using Rust.

I actually liked that he did it. So many other comparisons want to evaluate one language against the other language’s values. But I don’t want to know how well Zig can do Rust; I want to know how well Zig accomplishes its own goals, and what those goals are.

kccqzy

6 hours ago

Monads are where flat_map came from. A monadic programming style is where flat_map is used pervasively eschewing other APIs.

Syzygies

6 hours ago

For various purposes I work on a language comparison project that includes C and candidate successors such as Go, Zig. One question is the language to use for an archival port of a 1980's computer algebra system written in 32-bit K&R C. While Zig is a great debugging compiler, it's not yet stable enough to be the best target language for archival purposes.

https://github.com/Syzygies/Compare

So you're in a restaurant where you don't speak the language, you can't read the menu, but you see three price points for set meals featuring the house specialty. (Say, "Crossing the Bridge" noodles in Yunnan.) Which do you choose? My tour guide, the author Fuchsia Dunlop, later agreed with me this is obvious: The middle choice.

So you're choosing between Go and C23 as candidate successors to K&R C. They both have "royal blood". One got the name. Knowing nothing more, which do you choose?

The answer is equally obvious. The one that got the name also got the warts.

pjmlp

6 hours ago

Despite my usual rants, naturally C23.

Minimal rewrite due to the breaking changes introduced in C23 versus K&R C, while the others are a complete rewrite.

Even if the syntax is a bit of a kludge there are now ways to indicate bounds on function arguments.

d0mine

5 hours ago

Middle may be wrong (it is a marketing trick to add 3rd outragesly expensive option, to make 2nd option look reasonable). The correct answer is “it depends” (even how long you should spend on choosing may depend on context too).

For example, write in whatever language you know best, then translate to a more appropriate language using LLMs once the desired behavior can be checked automatically. It is a tactic that works in some cases.

flossly

an hour ago

who's your audience? who will read/use it?

weinzierl

6 hours ago

Two additional points:

1. Tooling (as an extension to the mentioned IDE support point). Zig and Rust are both praised for their tooling and I think rightfully so. The C/C++ interop story and the cross-compiling story in Zig are great. From the standpoint of a working practitioner though I think Rust is way ahead. Not surprising given that Zig is much younger, but something to keep in mind.

2. Compile Time Stuff: Here Zig is praised and Rust not so much. I think this is undeserved. Rust has much higher aspirations for their compile time features, namely that outcome must be identical regardless when the code runs. This is a very useful property but makes the task much harder and fundamentally incomparable with Zig comptime.

chaz72

6 hours ago

Are you saying that you think that Zig does not produce identical outcomes for comptime code regardless of when the code runs? What do you mean?

weinzierl

6 hours ago

Yes. For example in Rust it took a long time for floating point operations to be available at compile time and even now only a subset is. The reason is that a lot of energy and thought went into the issue of producing identical output (and what identical precisely means ) even when compilation is on a different processor than where the target runs.

As far as I know this is not a concern for Zig comptime.

rvrb

2 hours ago

there has been a ton of work on making comptime a pure and deterministic execution environment. I do not expect that work to stop. I don't know where you are getting the idea that it is not an important design consideration for the language.

to expand, if two different host platforms cross compiling to the same target platform have different results, I am almost certain that would be considered a compiler bug.

if you're pointing out that a runtime operation and a compile time operation might not agree, I'd be more interested in understanding when that would ever have any meaningful impact on anything. given the compilation is supposed to be deterministic, the difference can easily be addressed by comptime branching on target architecture in the rare case that it matters for your program.

weinzierl

an hour ago

Determinism and host/target agreement are two different properties. I meant the second one.

It's something Rust guarantees (without me having to take care of it e.g. by manually branching) and Zig does not.

rvrb

43 minutes ago

sure, it sounds like there is a difference here. I'm not aware of any stance by the Zig core team on the subject.

I am genuinely interested in a place that this matters for a program, or any practical consequence this has for an end user of the language.

no idea why your response was flagged originally.

simonask

20 minutes ago

Floating point hardware on different CPU architectures don’t produce bitwise identical results, unfortunately. That’s the big issue.

As far as I’m aware, they do produce semantically identical results, but something like the specific bit pattern of a NaN value can theoretically vary, and people might do fun things like encoding extra information in those bits.

weinzierl

34 minutes ago

The practical value is that moving a computation from runtime to compile time can then be purely an optimization, rather than potentially changing its semantics. Think generated lookup tables or numeric constants. Ideally f(x) means the same thing whether evaluated by the compiler or by the generated program.

nvme0n1p1

2 hours ago

What's your source for this? Comptime Zig code can do pointer casts and whatnot, all emulated as if run on the target bitness/endianness/etc. And any operations that are undefined on the target platform result in a compile error. I've never had an issue cross-compiling.

weinzierl

44 minutes ago

Endianness and pointer casts are the mechanical part and I would expect Zig to emulate them correctly.

Other parts, like floats are harder. This is where a difference shows. Rust is like: "Sorry, since we cannot uphold our guarantees, no transcendentals for you at comptime ", whereas Zig is chill about that and let you have your transcendentals even if results may differ between comptime and runtime. Different mindsets.

simonask

18 minutes ago

Just to note, Rust has had compile-time floats for a while now, but yes.

weinzierl

7 minutes ago

Yes, but no transcendentals on floats. You can add them and the like but you cannot calculate the sine.

chaz72

4 hours ago

Now I see this response, I responded to the other comment.

bruckie

6 hours ago

I assumed that it meant that if you ran code at compile time or at runtime, the results should be exactly the same given the same inputs.

chaz72

6 hours ago

And they believe Zig comptime wouldn’t do that? For the same inputs? I’d love an example.

vlovich123

6 hours ago

Traditional stuff in this space are:

* floating point differences between the build machine and the target. By far the most common

* endiannes - code assumes little median runs on big endian

There’s other more subtle issues that can crop up but those are the big two.

Not saying I agree though - those can happen anyway when you run on two different machines anyway.

chaz72

4 hours ago

I get it now. Important things but not things that I use often - hopefully addressed by 1.0.

weinzierl

3 hours ago

I'm not sure there is something to be addressed. Zig has just a different approach and Zig people different expectations.

In Rust we can expand what is possible at compile time without breaking existing code because we took a very careful approach only stabilizing what we are sure about. Some things will probably never be possible at compile time in Rust.

Zig is much more powerful but that also means they cannot take stuff away without breaking existing code and making comptime more restricted. So it is unlikely Zig will ever become like Rust in that regard, but that is ok - just different approaches.

chaz72

3 hours ago

I hear you but I’m not sure it’s as dramatic as that, it’s also been quite predictable in my use over the last few years since I haven’t happened to use floating point or different endianness.

vlovich123

3 hours ago

I think the Rust approach is incorrect - if the runtime behavior can change based on the machine that the code gets run on, why is it so important the build time behavior has such a restrictive definition? Especially considering build.rs can be used to bypass that definition anyway. It feels like a weird cut to make that I can’t figure out the understanding for. It feels like it inherited the const philosophy of c++11 without reexamining if it’s actually a good idea.

tcfhgj

2 hours ago

reproducible builds

smj-edison

3 hours ago

Doesn't comptime run under the target's float and endianness semantics? I need to check, but I believe they emulate the target when evaluating.

weinzierl

6 hours ago

sin(x) can produce different results at comptime and runtime. In Rust there is no sin(x) at comptime for precisely this reason.

chaz72

4 hours ago

Interesting, I hadn’t run across that but that’s probably just my problem domain. I did find this bug that looks like it was resolved a year ago https://github.com/ziglang/zig/issues/24184 and I have also read they are doing some other work on floating point that will hopefully address other cross-platform inconsistencies.

LoganDark

6 hours ago

Zig comptime feels easier and more effective in practice. I've had some fun const evaluating some stuff in Rust, but I needed to use a bunch of annoying imperative hacks because so much of the functional stuff wasn't supported in const context back then. It's probably a bit better these days.

For one of my crates I needed to have a build script make a bunch of lookup tables as separate files for me to `include_bytes!` because at the time I couldn't generate a bunch of floating point conversions in const.

tialaramex

6 hours ago

Certainly every new Rust release tends to have either new things which were stabilized as const on day one, or things which already existed but now have stable const.

The biggest constraint today on Rust's constant evaluation compared to where you'd expect is that trait implementations can't ever be constant, this obviously means you can't call SomeTrait::function in your constant, even if you can see the implementation of SomeTrait::function and if it were not a trait it'd obviously be constant -- but it also means sugar like Rust's for loop, which de-sugars into trait invocations, can never be constant today.

I think we can expect that to get fixed in the relatively near future, but I'd have said that last year too so what do I know.

If you have C++ experience you'd probably want a lot more. C++ is allowed to allocate inside constant evaluation, and I believe in C++ 26 it's now even allowed to persist the allocation to runtime rather than being required to always clean up during compilation, so that's a much bigger set of crazy things you can do at compile time.

LoganDark

6 hours ago

> it also means sugar like Rust's for loop, which de-sugars into trait invocations, can never be constant today.

Yep, that was my annoyance.

Const allocation is possible in Rust as an unstable feature. Not sure if you can persist it to runtime, though you can persist a reference which will become a static reference. I think it being unstable is why I needed `include_bytes!`.

estebank

2 hours ago

FWIW const traits are progressing quite nicely. It will also allow for Default to be used in const context, which is quite handy (and will integrate nicely with default field values, which only allows consts today in nightly).

vlovich123

6 hours ago

There’s a few comptime crates out there. Crabtime is iirc the most mature and popular

LoganDark

6 hours ago

I don't know if I would ever depend on something like that for a library crate. There's enough syn+proc_macro2 pollution in the ecosystem already.

tialaramex

6 hours ago

It does seem like maybe a crate with convenience macros so you can get a `&'static [Foo; N/size(Foo)]` rather than `&'static [u8; N]` and maybe even a delicious compile time "Hey jerk, that's not a valid Foo, you screwed up" if appropriate from your pre-baked data files, would be nice regardless of having more constant eval.

plqbfbv

6 hours ago

Maybe I'm not that deep into programming, but I don't understand the hype about Zig?

I programmed in rust a bit and can't say I'm an expert, but in my view rust mostly-solved the memory management problem at compile time and without a GC, and it works very well. The biggest con and cost I've always seen repeated so far is that "it's slow to compile", and I get that, if you're past 250 crates the final --release link tends to become noticeable, but there were improvements to incremental compilation.

On the other hand - looking at the syntax from this post - Zig feels a blend of javascript, python and golang syntax that still requires memory management. So a nicer-written C that inherits all the issues from C? From the post: no functional programming, data mutation, memory leak, double-free, memory corruption.

Personally I'd rather trade a couple minutes of final link every time when this is the other option.

pron

5 hours ago

> I don't understand the hype about Zig?

As a long-time low-level programmer, and as someone working on a popular mainstream language, I find Zig fascinating, and I also think it addresses a long-standing problem in low-level programming. I'll get to the problem later, but the fascinating part is its use of partial evaluation (comptime) as a single coherent mechanism that replaces a myriad of other partial-evaluation mechanisms (macros, templates/generics, constexprs). That one mechanism is the core of the language, like macros are in lisps, and that design - whether you like it or not - is revolutionary. It's never been done before (other languages have partial evaluation mechanisms that are almost as general, but they're offered in addition to, not as a replacement of, other features).

> rust mostly-solved the memory management problem at compile time and without a GC

"Mostly" does a lot of work here because 1., if you look at the implementation of very efficient, possibly specialised data structures - the very thing you reach for a low-level language for - they typically require unsafe, and 2., it still suffers from the problem C++ has had for decades, which is that over time, as program changes and evolves over years, things tend to drift toward the more general mechanisms that rely on malloc/free on an individual objects, and the program gets slower and slower (huge runtimes like TCMalloc help, but not enough, because they can't move pointers). This problem, of programs that start out fast, but after five or ten years of evolution need to spend a lot of effort to remain fast, is one of the things moving collectors were designed to solve, but they require moving pointers, which doesn't work in low-level languages that are not meant to have an FFI layer between them and the hardware.

To compete with the performance of moving GCs, which allocate through bumping a pointer, like on the stack, and free memory in bulk, low-level languages need to rely on arenas (which work based on a similar principle), and Zig is the first language that makes arenas almost user-friendly and hopefully sufficiently composable to withstand program evolution. Of course, time will tell how well this works in practice.

treyd

4 hours ago

> the very thing you reach for a low-level language for - they typically require unsafe

There's a formal proof asserting that if you keep up the safety invariants within an unsafe region then that will not infect other code, even in the presence of arbitrary other correctly-written unsafe blocks.

This means you can build abstractions on top of these low-level primitives to keep it contained, so consumer code never has to even think about or know there's unsafe blocks in it. The type system lets you build very powerful abstractions so these go a long way.

There's a lot of woo-woo scare quoting around how much you actually have to use unsafe code in Rust. It's fairly uncommon to actually have to reach for them in practice. Most of my usage ends up being things like converting a &[u8] to a &str when I know it's already valid UTF-8 so I want to skip the linear-time validity check. Very rarely do I have to build data structures with complicated pointer juggling, because there's often a library that already does what I need!

> which is that over time, as program changes and evolves over years, things tend to drift toward the more general mechanisms that rely on malloc/free on an individual objects, and the program gets slower and slower

What are you talking about? I've never encountered this and I've been using Rust for 10 years.

jey

3 hours ago

> > which is that over time, as program changes and evolves over years, things tend to drift toward the more general mechanisms that rely on malloc/free on an individual objects, and the program gets slower and slower

I think the idea is that a small program can organize its allocations and data structures to minimize number of calls to malloc, e.g. with preallocated workspace structs, or slab allocation, and similar approaches. But as a program gets bigger, there's a pressure to have looser coupling, to have subsystems with simple convenient APIs which leads to them doing on-demand malloc calls internally, rather than having consumers pre-allocate their needed workspace. Because that kind of workspace management results in more complex APIs and more burden on the consumer.

That said, I don't really believe it either, at least for the kind of codebase where it would matter (scientific computing, in-memory DB server, etc). A codebase that places an emphasis on minimizing heap operations in hot codepaths can do so by consistently using workspaces and allocation-avoiding APIs. I don't think it's so difficult really, but it does take a conscious design decision to do so. But writing something like a web browser in this way could be annoying due to most data having wildly variable sizes, and zig's arena concept would be very handy -- but rust has crates like bumpalo for that purpose.

My personal mantra: "Think in FORTRAN, code in Rust/Julia/C++". But I'm mostly working on HPC-style code where I don't have to do with wildly varying input or output sizes.

pron

3 hours ago

> but rust has crates like bumpalo for that purpose.

Except that's not composable - not only do you need specialised data structures, but all (transitively) allocating calls need to be specialised. That's the exact same issue we have in C++, and that's the issue Zig seeks to address. BTW, just the other day there was a post here about a language with another interesting approach, but I have yet to give it a close look: https://github.com/aardappel/goose/

> But I'm mostly working on HPC-style code where I don't have to do with wildly varying input or output sizes.

There you have it. The problems arise more quickly in concurrent rather than parallel code, and when there are lots of features added over the years that touch the hot paths.

> in-memory DB server

Actually, here there can be big problems (as it's also about concurrency rather than parallelism). Last week a colleague of mine looked at Moka and saw that it could only offer half the throughput as Java's Caffeine at the same latency and RAM footprint (almost; the Java program used 5% more RAM). When he looked into it, he saw that over 40% of the program's CPU was spent on the epoch-based reclamation.

treyd

an hour ago

> not only do you need specialised data structures, but all (transitively) allocating calls need to be specialised

Most crates for containers will be written such that the container types take an optional allocator type parameter that defaults to the global allocator. You can set it and it transparently uses the other allocator.

To improve the ergonomics, you'd define local aliases that use that allocator.

    type MyVec<T> = Vec<T, A = MyAlloc>;

solomonthewise

an hour ago

I works in pretty low level OS code. I promise you most of our code would be unsafe. And using unsafe in rust is less ergonomic then using zig or c++.

We could use rust. But it wouldn’t give us anything.

simonask

8 minutes ago

This hasn’t been the finding of the R4L project. Go look at their code, it’s shockingly safe outside of the parts that interact with extern “C” symbols, which naturally need to be unsafe.

pron

4 hours ago

That's always been true in all the safe languages with unsafe escape hatches, except here these "primitives" are the main reason to reach for a low-level language in the first place - because they presumably require the control that low-level languages offer. Combining them in the same language might appeal to some and not to others who think that the high-level, safe parts are unnecessarily complicated because it needs to integrate with the low-level parts, and the low-level parts are unnecessarily complicated because they need to integrate with the safe parts. Anyway, some like this and some don't, but my point is that it's not "mostly solved".

> What are you talking about? I've never encountered this and I've been using Rust for 10 years.

Okay, but I've been doing low-level programming professionally for 25 years, and have encountered this over and over in large programs (over 500KLOC) as they evolve.

treyd

4 hours ago

That's just not an accurate description of how you write Rust in practice. There's no separate "high level" and "low level" parts/forms of the language any more than the software development process already is all about building abstractions. You should be doing this in Zig, too.

It's just that sometimes some of the abstractions you need to build go outside what the ownership and borrowing system can model. And when you don't need to do that (which is 99% of the time) you also get all the benefits of the ownership/borrow system for free.

pron

3 hours ago

They're not separate forms but they are separate modes, and it is precisely because the language tries to fit both these modes into the same language that both suffer. I fully understand the goal of trying to unify these modes into the same language (C++ does the same thing), but there have always been very experienced people who like this approach and those who dislike it, hence it's not "solved". Something is solved when there's a broad consensus it's solved, and there isn't one here.

I mean, someone can think it's solved for them, but if they're asking why others don't see it the same way and why many expert low-level programmers are at least intrigued by Zig, this is why. I prefer a simpler high-performance high-level language for high-level things, and a simpler low-level language for low-level things, and I dislike the C++/Rust approach of combining them into one complicated language. Some may think you get the best of both worlds; others, like me, think you get the worst of both worlds.

treyd

2 hours ago

Can you point to a specific example that ends up being a "worst of both worlds" in your perspective?

pron

5 minutes ago

I don't know exactly how specific you want to be, but sure, because we've come across this countless times in C++, which suffers from the exact same problem.

Suppose you're writing a program that's mostly high-level, say some kind of concurrent server, and it's large-ish, say around 1MLOC (most C++ programs I've worked on were significantly larger). Because the language is also a low-level language, it has low-level constraints, so:

1. It needs to use an AOT compiler, and consequently to get good performance you need to use less general mechanisms, such as direct (as opposed to dynamic) dispatch and even manual monorphisation (with generics/templates). These are viral, so they have to be carefully chosen (you can't monomorphise everything or you'll get machine code explosion). Five years later you need to make a big change that requires more generality, and then you either have to reconsider all of your manual optimisations, which is expensive, or go for more general constructs (dynamic dispatch) and the program gets slower.

2. It needs to use machine pointers (i.e. you can't enjoy a moving GC), and so you try to use the stack as much as possible (which you can't really do for anything dynamic), or suffer the high cost of malloc/free on individual objects. As the program evolves, you need to make things more general, and objects that could live on the stack now need to go on the heap, and objects that lived on the heap now may need to be shared among threads, in which case you often add the additional cost of refcounting GC. Of course, you want to use arenas in many cases, but they're very, very hard to use in C++ and Rust.

You'd be better off - performance-wise and maintenance-wise - with a good optimising JIT and a moving GC. This was exactly a problem with many C++ programs that didn't really need a lot of direct hardware interaction - everything worked great for a few years, and then the evolution and maintenance costs became really high (or the programs became slow).

Now suppose you're writing something low-level, i.e. you really need to interact with the hardware and/or OS directly a lot, and want to control everything - where everything is in memory, exactly when it's initialised, exactly when it's freed, exactly which operations are executed and when. But now you have a language that's also high-level, so it has a lot of implicitness that hides from you the things you want to see (and in Rust's case, you lose the safety). Best case scenario, you rely on disciplne and avoid implicit features, but then you also need to avoid much of the standard library.

dnautics

2 hours ago

> There's a formal proof asserting that if you keep up the safety invariants within an unsafe region then that will not infect other code, even in the presence of arbitrary other correctly-written unsafe blocks.

In general "unsafe" does not compose.

"if you keep up the safety invariants within an unsafe region"

This condition is doing a lot of heavy lifting.

treyd

2 hours ago

Here's an article about the research on it which lays out the properties in simple terms: https://smallcultfollowing.com/babysteps/blog/2016/10/02/obs...

I'm curious why you think that statement is doing heavy lifting. It's much easier to write and verify that a few lines of code are correct than it is to write and verify that an entire program is correct. But that's the norm in C and Zig, and historically people haven't been very good at it. That's why we try to do it as little as possible.

pjmlp

6 hours ago

Basically you pick the type safety that Modula-2 or Mesa already offered in the late 1970's, repackage it with comptime and more C like syntax, and have a whole legion of new devs jumping into it.

Note that AT&T, where UNIX and C were born, the language they were researching as C replacement was Cyclone, not something that is not much different.

qudat

4 hours ago

As someone who primarily writes in TS, Go, Python, I agree with your assessment: it looks like those languages. The reason I like Go is the reason I like Zig: the language is relatively simple and feels like C.

The C interop is a huge win as someone who wants to do more posix/wayland projects.

hinkley

5 hours ago

Rust could not work in a world where compilation was a single threaded affair. I think it exists now instead of in the 90’s and 00’s in good part because of this.

I don’t think it’s an accident that it has succeeded as multicore took over. We are now well past a point that a task that can be split with 70% efficiency into multiple parallel tasks is 5-10x faster than the optimal sequential solution. Expensive multicore machines existed when Rust was a baby but it didn’t really catch on until 4 core was common in consumer hardware. And now I have an ancient laptop with 16 cores.

But Rust is also good for producing correct code to run on those systems. So it benefits twice.

bunderbunder

5 hours ago

Zig is aiming to be lower level than Rust. As the project homepage prominently advertises, it has no hidden memory allocation or control flow. It also gives more control over how memory is allocated, which is potentially useful in applications with particularly tight performance requirements.

Kelley first created Zig when hr was working on a digital audio workstation and found most existing languages to be awkward for working with particularly hard real time requirements, but still wanted something more modern than C. Im speculating here, but I believe its advantage over Rust for that specific application is that you have tighter control over exactly when memory is allocated and deallocated, and how data is laid out in it. Rust wants to tie allocation lifetimes to scope in a very fine grained way that I would guess is beneficial the vast majority of the time, but does still make it harder to reason about when you’re about to stall out the CPU while the allocator does its thing.

packetlost

5 hours ago

> it has no hidden memory allocation or control flow

Rust had like 3 allocating types total. If you aren't working with extremely deeply nested 3rd party types it's trivial to identify when allocations happen. Hell you could throw a lint rule together in like 5 minutes to warn on it if you're really worried. Besides Drop (excluding async) is there even any hidden control flow?

> Im speculating here, but I believe its advantage over Rust for that specific application is that you have tighter control over exactly when memory is allocated and deallocated, and how data is laid out in it.

Rust has almost exactly the same semantics for controlling allocations and deallocations, it just prevents you from screwing it up and not freeing something or using the allocation after freeing it. You still have to pass around your reference in your call stack until you no longer need it.

> Rust wants to tie allocation lifetimes to scope in a very fine grained way that I would guess is beneficial the vast majority of the time, but does still make it harder to reason about when you’re about to stall out the CPU while the allocator does its thing.

It's really not substantially different. You allocate ahead of time or don't allocate at all. The only real difference is you might want to use an Option instead of an uninitialized pointer because it's semantically more correct and harder to screw up.

bunderbunder

3 hours ago

But here I feel like we’re at risk of heading down the same old doom spiral that plagues any conversation about programming languages when people try to treat it as a competition: getting pedantic about what’s technically possible in a language. It’s much more interesting to talk about how a language wants to be used.

So, in the case of Zig, every function that wants to be able to allocate or deallocate heap memory needs an explicit reference to an allocator. That means that you can tell whether a function might allocate memory from its signature. It also means that changing a function so that it can allocate is explicitly a breaking change.

That’s a really interesting design decision. And the reasons why someone would or would not want something like that baked directly into the language are so much more interesting than bickering about how technically with proper discipline you can have that kind of control in any non-GC language.

dnautics

2 hours ago

> That means that you can tell whether a function might allocate memory from its signature.

In practice that's not the case, as many objects own a reference to their allocator. It's still explicit, but it might be hidden in the signature, especially if you have some sort of interface that can take an allocating and a nonallocating data structure alike.

orf

2 hours ago

I agree with you, but it’s not baked directly into the language? It’s just a convention and a shared trait?

ponkpanda

an hour ago

Not original commenter but the reality with zig is a little in between being simply convention vs. a language requirement. Is it a language requirement? no.

However, there's no global allocator in zig. You simply cannot call the language's equivalent of malloc() because it doesn't exist - at least not as a global symbol. That leaves you with three choices: 1) Define a global allocator; is a valid choice and would make a zig program more like C, C++ or Rust in terms of not having to think about scope-level allocation patterns 2) Pass an allocator into that scope (this is the community convention) 3) Create/instantiate an allocator itself inside that scope

(1) would be valid, though may not be idiomatic; global allocator like malloc becomes an opt-in

(2) Expensive and inefficient for most scopes, though not all.

(3) cheap, idiomatic but potential for noise/boilerplate

sennalen

5 hours ago

I wish people would stop inventing new languages for nostd when we have nostd

pixelesque

3 hours ago

> Rust had like 3 allocating types total.

Seriously? Categories of types maybe, but literal types it's more than that.

Even closures allocate if they need to capture their environment.

steveklabnik

2 hours ago

It’s actually the opposite: the language has zero allocations in it. Allocation is entirely a library concern.

When you want to have a closure allocate an environment, the closure itself does not: the Box you wrap it in, which is a stdlib type, does.

kllrnohj

5 hours ago

> Zig is aiming to be lower level than Rust.

Zig and Rust are equivalently "low level". Zig isn't any closer to the hardware than Rust is.

> but I believe its advantage over Rust for that specific application is that you have tighter control over exactly when memory is allocated and deallocated, and how data is laid out in it.

Rust gives you all this, too.

Zig's primary (possibly only) advantage over Rust is that it has much faster compilation times.

maleldil

4 hours ago

Comptime is a big one too. You can achieve similar things in Rust with generics and macros, but comptime makes certain things easier (and other things harder).

kllrnohj

5 hours ago

Zig is exciting to people that still actually like C. Is that a rationale choice? Not very often. Is it a wrong choice? Also again not very often. At least, not for anything in scope of a solo dev.

The industry where it seems strongest positioned is embedded. Will it actually break into that domain? No idea.

logicchains

4 hours ago

If you're writing ultra low latency code you basically want everything allocated from an arena, with different arenas for different kinds of objects. Zig comes with this built in, while Rust makes it extremely unergonomic to do safely (due to the lifetime system).

kibwen

3 hours ago

Arenas in Rust work very well with lifetimes. In fact arenas benefit greatly from lifetimes, because lifetimes allow them to uphold the usual Rust safety guarantees about preventing use-after-free. For example, here's the bumpalo crate in action:

    let mut arena = Bump::new(); // create arena
    
    let foo = arena.alloc(Foo { x: 42 }); // allocate item in arena
    
    bump.reset(); // clear arena
    
    foo.x += 1; // compiler error preventing use-after-free

applfanboysbgon

4 hours ago

> So a nicer-written C that inherits all the issues from C?

And inherits all of the benefits of C. C is the foundation of the computing world. "C, but not built 50 years ago" is, by itself, a tremendous value add to a programming ecosystem that has largely abandoned attempts to write a truly performant language in favor of handicapping programmers with fully automated safety.

Rust is a low-level language for people who don't write low-level code. Zig is for those who do.

slopinthebag

6 hours ago

yeah to me zig exists as a counter-reaction to rust. which means avoiding both the good and bad things rust does. and rust does a lot of things right, so...

IncreasePosts

5 hours ago

Rust is for devs who think "if only c++ had a few more features, it would be perfect". Zig is for devs who think "if only C had fewer features, it would be perfect"

slopinthebag

5 hours ago

ironically rust has fewer features than c++ and zig has more than c

IncreasePosts

4 hours ago

Give it a chance! Rust hasn't even had its bar mitzvah yet and C++ is already buying a Porsche during its midlife crisis.

Yes, on paper zig has more features than C, but what zig has is explicitness. C has a big murky space of implicitness. For example, there might be 5 different zig features which can be used at various times you might use a void* in C, buy the conceptual space of void* fully contains(and then some) the spaces of those zig features.

tialaramex

7 hours ago

I think when we're looking back on the 2020s we'll be struck by the Allocator obsession

All of the Handmade "C successor" languages seem to have this obsession, including not only Zig but Odin, C3 and Jai.

For some toy problems you can do clever allocator tricks and get a huge perf win. For example Jai and Odin both seem to really want you to write code which can throw away a "per-frame" arena periodically so they're not paying to track allocations in the arena because they're all thrown away at the same time.

But a lot of real world software just isn't that simple. This doesn't make such features worthless, it just means they're one of a thousand tools the experienced developer could want in their toolkit, not really deserving headline status.

pton_xd

7 hours ago

AAA games use allocators extensively, and they are more complex pieces of software with higher performance requirements than nearly anything else out there. So I'm not sure what toy problems you're talking about.

Allocators have been in wide use long before the 2020s but I agree there does seem to be a resurgent interest lately. Although I would argue it's part of a more broad trend of focusing data driven design. Which makes sense because accessing main memory is one of the slowest things your program can do.

simonask

6 hours ago

> AAA games use allocators extensively

They do, but not necessarily together with generic, standard containers.

When you find yourself wanting a nonstandard allocator, you usually want it because you want it to have some interesting property. It's not necessarily trivial to fit that into the interface of something like `std::vector`, or `std::unordered_map`, etc.

Here's my take as a game developer: 99% of use cases for custom allocators are scratch allocators for doing stuff within a frame. 95% of those are much easier to serve by just amortizing allocations by storing things in an `std::vector` (or equivalent) that gets cleared every frame. You can use linear storage to back many interesting data structures, including queues, ring buffers, priority queues, binary heaps, etc., and that's more than enough for a large number of systems in a game.

The overwhelming majority of the time, more complex data structures (like hash maps etc.) have a longer lifetime than the current frame, because the whole point of using them in the first place is to amortize lookup time across frames.

kllrnohj

7 hours ago

And those games do so in a language (C++) where allocators is not a headline feature, and is barely even supported at all in the standard library.

The important part is a language where the standard library isn't special, and Rust has this property, too. So in domains where things like per-frame allocators are useful, you can still have them. That capability just isn't cluttering up the more common path where that isn't useful.

lefra

6 hours ago

I never used it in practice, so maybe the support isn't that good, but I was under the impression that it was possible to pass custom allocators basically everywhere in the STL. See for example the definition of a vector here:

https://en.cppreference.com/cpp/container/vector

kllrnohj

6 hours ago

std::allocator was so painful to use it was almost always easier to just reimplement the container instead (especially for trivial ones like vector).

I haven't used the relatively new C++17 polymorphic_allocator, though, maybe it fixes this.

pixelesque

3 hours ago

> and is barely even supported at all in the standard library.

What does that mean?

jmull

4 hours ago

Allocation happens. You can leave it to the compiler, the runtime, or let the code control it.

The automatic solutions are usually pretty good and usually the right place to start. But if performance is a priority, you want options.

BTW, “per-frame arena” is part of a general pattern of a repeated interval of work doing significant allocation. This is really common in software of all kinds… servers that process requests (like web servers and database servers) and typical command line tools.

bunderbunder

3 hours ago

Way back in the aughties I saw an interesting analysis (on a now defunct blog) indicating that under typical usage C# implementations tend to outperform C++ implementations in long-running business applications. The supposed reason was that C#’s compacting GC keeps the cost of new allocations fairly constant. By contrast, in C++ under typical use every new allocation requires probing for a sufficiently large block of free memory in an increasingly fragmented heap.

I haven’t tried to replicate this for myself. And, even assuming for the sake of argument that it was definitely true back then, a lot can happen in 20 years. But still, it does speak to wanting options when performance really is critical.

boomlinde

5 hours ago

> But a lot of real world software just isn't that simple.

Zig doesn't force or even tends to prefer one way or another. If I want unassuming heap allocations that can be reclaimed in any order, there's an allocator for that. If I want an arena to discard at the end of something like a request or a video frame, there's an allocator for that. If I want to use a fixed backing buffer for the allocations, there's an allocator for that.

The point is that the standard library doesn't assume one or the other, which seems good if the problem is that "real world software just isn't that simple" in the more general sense that there's no one-size-fits-all allocation strategy.

NetMageSCW

2 hours ago

But for how many software domains are allocators more specialized than a single built-in general purpose one so important it should be the focus of your language?

sortoflog

3 hours ago

I’m not so sure the usefulness of custom allocators really relates to the complexity of the problem. Take the scratch arena example: ifaik it’s just free performance (& simplicity) whenever you need dynamic allocations with lifetimes that begin and end on a critical path. Presumably this is why Jai/Odin focus on this situation so much since it applies to per-frame stuff in videogames, but generally I’d expect this to come up more often in nontrivial problems.

philippta

4 hours ago

> For example Jai and Odin both seem to really want you to write code which can throw away a "per-frame" arena periodically

You can generalize this far beyond per-frame semantics. Think per-http-request, per-pubsub-message.

Per each, you can create a new virtual.Arena, set it as your context.temp_allocator and use it for the entirety of the request or message. Afterwards, throw it away.

pixelesque

3 hours ago

Some of this surely stems from Rust not originally supporting allocators at the standard API level until later. You can manually create your own, and create/modify your own containers, but until GlobalAlloc came around it sometimes involved doing things like passing through Arc<Allocator> through to things if you wanted multiple things to use a single one.

Sometimes using a global one isn't the best thing (it's often a good idea to specialise base on allocation size, reuse and lifetimes), but I've used them quite a bit in C++ over the past 16 years doing HPC for graphics, rendering and simulation, so calling them only useful for "toy problems" likely shows you just haven't found a need for them in what you've been doing.

jcranmer

5 hours ago

I'd word it slightly differently, as something like the Allocator Effect System obsession. It's not so much that allocator tricks don't have a role in modern software--all of the large applications I've worked on rely on things like arena allocation at least some of the time--but rather that things like making container types generic over allocators seems to be more trouble than it's worth. I've never seen anyone use anything other than the default allocator for STL types, for example.

convolvatron

5 hours ago

actually large programs that are persistent is where you really care. kernels and databases often use explicit allocators because there is so much policy wrapped up in allocation.

this goes back decades, its not just a feature of the 2020s. as a systems programmer I always want this, and the idea that allocator state should be completely hidden and implicit is shortsighted.

but yes, it is a bit onerous to pass around allocator(s). the real complaint that I have is that if 'malloc' is global and a compiler primitive, then we can do things like coalesce allocations and have compiler managed lifetimes when appropriate.

explicit allocators are an important lever, its not clear to me that we could never find a way to make them possible without the minor downsides.

NetMageSCW

2 hours ago

Making allocators part of a type or scope instead of in every function call might be a start.

slopinthebag

5 hours ago

yes i've noticed this as well. they all stem from a heterodoxical corner of the programming community who are obsessed about performance. this isn't a bad thing, considering how slow modern software is! but this group believes performance is a memory issue, which is true for some software but not all. i think these languages can be really useful for realtime applications like gaming where allocation has a real cost, but the so-called "pointer jungles" are probably not the reason why microsoft teams takes a trillion cpu instructions to boot.

jauco

2 hours ago

Given that the author mentions both using helix and zig encouraging larger files with more content I’d be curious to know how they navigate these files in helix. The thing that keeps me from using it is lack of code folding, which I notice I use a lot when navigating larger files to zoom out.

spider-mario

7 hours ago

> The first thing that caught me off guard — and honestly, who would’ve expected this to be the memorable part — was IDE support, or the near-total lack of it.

I would have completely expected that.

sgarland

an hour ago

From TFA, on combinators vs. loops:

    items.iter().enumerate().filter(|(_, i)| cond(i)).map(|(idx, i)| Pointer::idx(i, path, idx)).collect()

    while (i < cursors.len) {
        if (actual_index < arr.items.len) { cursors[i] = .{...}; i += 1; }
        else iteration.remove(i);
    }
I’ve been slowly learning Rust, and this style is my main gripe against it, because I feel like I’m being gaslit. Its proponents praise its readability and ease of use, and just… no. It looks deranged. A simple loop is immediately obvious to anyone who’s programmed in any language. Even Python’s list comprehensions are loop-ish.

matja

37 minutes ago

.len doesn't work on a list (because it might mislead you about the efficiency of the operation if it did exist), so it's better to use iterators - so then you can change the underlying type without changing your code.

But then, saying while "let Some(item) = iter.next()" everytime is tedious, so they give you .iter() - for any type that is efficiently iterable.

Nothing stopping you using a manual loop that you need to update if you change the container type.

himata4113

an hour ago

  items.filter(|(i)| cond(i)).map(Pointer::idx).collect()
you can probably get away with this if you implement a Trait, not sure how but I know for a fact this is possible, idk why there's an enumerate there when you aren't even using it.

items is already an iteratible so you can do direct .filter on it as well

tl;dr if the code looks ugly you're probably not taking advantage of a language feature that allows it to look pretty.

sgarland

an hour ago

This is in fact more readable, thank you.

I still don’t know that I find it more intuitive or readable than the simple loop, but it’s much less awful than before.

ozgrakkurt

7 hours ago

Would recommend learning how to use arena allocation. You would have patterns like:

fn run_query(alloc) {

   arena = init_arena(alloc);

   defer arena.deinit();

}

simonask

6 hours ago

Step zero of using arena allocation is to build realistic benchmarks so you can measure if it's worth the trouble in the first place. Standard allocators are incredibly good these days, and even plugging in mimalloc or jemalloc will be much less work, and much less error prone.

boomlinde

4 hours ago

I don't know about more error prone. Benchmarks completely aside, freeing a batch of stuff you've allocated in a single place makes it easier to manage memory. I think it should be preferred wherever it's an option for that reason most of all. The "killer app" is something like an arena allocator that lives for the duration of an HTTP request.

simonask

2 hours ago

I respectfully disagree. The technique has its place, and I use it once in a while, but whether it makes a positive difference for performance is highly sensitive to a number of factors.

For example, bump style allocators allocate very quickly, but at the cost of higher memory usage and therefore sometimes worse cache locality.

The only way to know is to actually measure.

boomlinde

29 minutes ago

You disagree with a point I never made. My point is that they enable easier memory management, regardless of whether they're more performant. I really don't understand how that point didn't get across. You must simply not have read what you responded to.

ozgrakkurt

3 hours ago

I meant to suggest using arena allocation for everything and not even using a malloc style allocator. Just getting memory via memmap at program start and then using arenas for everything after that.

It makes it much easier to avoid lifetime mistakes in my experience.

Using arena allocation also makes me think more about how much memory I am using and how much memory I should be using etc.

It is hard to benchmark it against just using a global allocator because it is a structural change to the whole codebase.

simonask

2 hours ago

If this works for the programs you write, that’s great. It does preclude you from using many great data structures with potentially better performance - especially hash maps. Rehashing is pretty detrimental to most arena allocators you can think of.

slopinthebag

5 hours ago

i think the reason is more that arena's let you manage lifetimes in groups instead of pointer chasing. if you have to manually manage memory, it's eaiser to manage a small number of arena objects instead of a large number of individual objects.

eg https://www.dgtlgrove.com/p/untangling-lifetimes-the-arena-a...

that being said, it's even easier to not manage any lifetimes at all :)

although i suppose some will say that you still manage lifetimes in rust, you just have full support from the compiler to make sure you do it right. that seems better to me than relying on simplification to ensure you don't make mistakes.

elendilm

2 hours ago

> It turns out I’d simply forgotten how straightforward it can be to rely on bare CLI tooling.

Happy for you.

CLI tooling counter intuitively makes for very less friction especially when you are moving very fast.

gigatexal

7 hours ago

“ The language is different from Rust (who could’ve thought that, yeah), but it left a genuinely good impression. It’s straightforward, modern, and blazingly fast. I believe it has real potential to become the true successor to C. On the other hand, it’s still young, and it shows: the shape of the language itself feels unfinished in places, and I suspect it’ll pick up more of the cooler quality-of-life features and syntax sugar as it matures.

As for me, I’d like to keep contributing to the ecosystem, and I will, whenever I come across a project worth building.”

Idk I don’t write either well enough to have a hand in this but losing out all of this for more Imperative stuff seems like a step back.

“ No functional paradigm

Rust is technically an imperative language, but it draws heavily on functional concepts: zero-cost iterators, lazy evaluation, ADTs, pattern matching, monadic types, traits, closures, and so on. Having also spent time with Haskell and Erlang, I’ve become fairly inclined toward the functional style, and it shows in this library. It leans heavily on FP idioms:

Monadic error control via combinators like Queryable and related types Monadic-style data types like Data<T> with map, flat_map, reduce, and friends Pure, immutable transformations Combinators over iterators instead of loops Closures for local abstraction Declarative macros as a small embedded DSL Sum types and product types”

cosmic_cheese

7 hours ago

I’m more than a bit out of my depth discussing the topic, but I’m not sure than imperative-dominant languages will ever really go away or that functional-dominant languages will ever become as popular as C and C++. Ugly as they may be, imperative languages seem to be grokked by humans more readily and are more often than not “good enough” for the most part so it’s difficult to see them losing substantial momentum.

pyrolistical

7 hours ago

Until there is a machine that is natively functional, there is always going to an incentive to go lower level for more performance.

Even hardware (GPUs) that functional language could trivially exploit, it’s still higher performance to write low level code and manages all the memory imperatively

pjmlp

7 hours ago

Modern and blazing fast, what we lost leaving behind languages like Modula-2 and Object Pascal, having newer generations to think C and C++ were the only compiled languages alternatives to scripting languages.

touisteur

7 hours ago

I miss the years of writing CLI tools, web servers and clients, in Ada (and of course, real-time complex distributed system...). Felt so simple and right and fast and robust. The code is still readable today and maintaining it is a zero effort today. Clean Java without the enterprise BS was a close second in ease of programming - boilerplate be damned.

I'm glad NVIDIA found a way to make GPUs programmable and got us out of the shaders tarpit, but did it have to be C++...

pjmlp

7 hours ago

Also a good one.

jstimpfle

7 hours ago

You repeating this weird strawman take a million times doesn't make it true. Why don't you finally just put out some genuinely interesting projects demonstrating how everybody was doing it wrong, so people can make up their own mind and finally be convinced. There must be some true magic in those languages and platforms you mention, that should offset the pain of writing in upper case and with super long KEYWORDs everywhere, and to offset the cost of switching to a culture that has way less mindshare and way less of a software ecosystem around it.

FWIW I've actually worked for 6 months on a large old Delphi project. It was some performance work that, as almost always, mainly required getting the language crap out of the way. In the end I got the job done (100x-1000x speedup) but I wouldn't want to switch back to this ecosystem: Licensing costs, weird language warts there too. A slow moving ecosystem. Ultimately, I just need something that does what I tell it to do, reliably and fast, and that doesn't get in the way.

pjmlp

7 hours ago

If you don't like, press PgDn.

AndrewDucker

6 hours ago

People should reply on Hacker News. If you disagree with someone's opinion and you have reasons for doing so then the right thing to do is share them.

pjmlp

6 hours ago

Sure, except there is a bit of history here, this isn't the first exchange, and I am not bothering to reply back as we aren't going to change opinion on C vs safer languages until we leave this realm.

Thus we can keep filling HN with pointless comments or move on.

jstimpfle

6 hours ago

There is plenty history of you repeating the same strawmans literally thousands of times. I've just never seen _anyone_ on HN (or elsewhere for that matter) make such claims?

pjmlp

5 hours ago

Here goes again, better improve your Algolia search skills if you cannot find anyone else.

jstimpfle

5 hours ago

I don't even have to search. Given the frequency of you repeating the same old tired stories, one is bound to stumble over your comments every other day.

api

7 hours ago

I would say Rust is a functional language that has been hammered into the shape of C++. It also draws heavily on ML.

I get why, and it makes it a better fit for its obvious “C++ reimagined, cleaner, and better” niche.

karmakurtisaani

7 hours ago

Off topic, but I remember fondly the pre-LLM days when I used to love reading about programming languages. I never got a chance to professionally work with Rust, but made some cool hobby projects with it. Would have eventually tried out zig too.

Now it all feels so pointless though. Like memorizing rules to do mental arithmetic. Sure, there is still use for language expertise, but not enough to get excited over new concepts and ideas.

alembic_fumes

6 hours ago

I relate to this.

For years I used Rust as my hobby-programming language and loved it greatly. I never managed to land a job working with it full-time, because either the work was too niche or it didn't pay enough, or I was simply too comfortable where I was to change. And now that I finally have enough discretion over my technology choices to run a "proper" project using whatever tools and languages I want, it is not me but the AI that writes all of the code.

There's a part of me that feels a quite sad about all this. It's almost as if there actually all along existed a real final deadline on finding that "dream job". And I missed it. And while I expect this one miss to be just a small piece in the grand picture of things that we're going to lose or have already lost to the zeitgeist of agentic SWE, it feels big to me. It was my professional dream, while I still had professional dreams.

NetMageSCW

2 hours ago

I seemed to have been through this pattern in my interests multiple times and just wait in the between times for something of interest. For hobby/interests I started with programming languages and then moved into understanding hardware and then mobile devices. I wrote HP 41 s/w, HP 48 s/w, the first public dethreader for HP 48 ROMs, Newton s/w, Windows CE s/w, jailbreak s/w and on device iPhone 1.0 s/w (pre App Store), but have been waiting for the next thing since iPhone 5 or so. In languages I went through a lot as well and now am waiting on C# to be improved.

karmakurtisaani

5 hours ago

Yeah, this is exactly it. Not earth-shattering, but personally depressing. My solution has been to start a career switch.

ratorx

7 hours ago

I think (for now), it is still relevant. A language is an abstraction, and a good abstraction, like a good LLM harness, can be quite valuable.

Let’s say I’m writing some concurrent code with an LLM. I’d probably feel much safer having it write Rust, rather than C. So even in a post-LLM world, languages will continue to evolve as long as abstractions can be improved.

karmakurtisaani

7 hours ago

But do you still have enthusiasm for finding out about new language features or concepts? I'll do what it takes to get the job done, but the passion for it is totally gone.

frje1400

6 hours ago

Yes, as an example structured concurrency in Java (final release in Java 28 perhaps). I think that could totally change how we write concurrent code in that language.

I just upgraded a less important service to Java 27, a few days after its release (several nice features). It's cool how easy it is to upgrade nowadays.

That an agent writes most of the code doesn't mean anything to me here.

My examples are Java because that is the main language where I work.

karmakurtisaani

6 hours ago

Thanks for the perspective.

frje1400

6 hours ago

You're welcome. I now feel inspired to try to change your mind, if you don't mind.

I would argue that "concepts" actually are more important than ever. Let's take my structured concurrency example. It doesn't matter here exactly what is, but if it ends up being as important as I think it will be, I likely want to write most concurrent code that way going forward.

However, it will likely be years until agents go to it unless deliberately steered in that direction. And if I want to make agents write it, I need to review it, and if I'm going to review, I need to understand it.

I think this is why I'm not pessimistic about the profession, it still feels like what I'm doing and learning matters.

za3faran

6 hours ago

There's a couple of languages I follow their roadmap and I find exciting, including Java and C#. I don't feel there's proper justification of writing backend services in something like python or ruby anymore, for example.

NetMageSCW

2 hours ago

I found C# exciting when it was new and adding features that were important to me, but now it feels like it has become bogged down with chunks just abandoned on the alter of backwards compatibility (Expression, anyone?) and adding features I couldn’t care less about (null safe code, pattern matching, etc).

karmakurtisaani

6 hours ago

Yeah, I also try to follow what's new. But it's more like "Ok, that's good I guess." Rather than "Cool! Looking forward to applying this in my next project!"

pjmlp

5 hours ago

Never was really, at least if performance matters.

tonyhart7

7 hours ago

nothing stopping you to code manually

karmakurtisaani

6 hours ago

Nothing stopping me from doing a lot of things. But is there any point to it?

layer8

6 hours ago

Meaningfulness comes from what you care about. I wonder why you were interested in programming-language concepts before but now (apparently) stopped caring about the code. The code still remains the language that communicates the actual program logic.

karmakurtisaani

5 hours ago

I guess a large part of my motivation was that it makes me a more effective and skilled programmer. There are countless of interesting things out there, and the motivation to focus on one comes from what you do with it later on.

rgoulter

6 hours ago

> Sure, there is still use for language expertise, but not enough to get excited over new concepts and ideas.

Programming and learning new things can still be fun in the era of agentic coding.

With LLMs, I get to quicky ask: what would this look like? Why do it that way? If you suspect that the LLM isn't doing it the right way, you can still investigate that yourself.

e.g. the other day, https://rhombus-lang.org/ was mentioned on HN. With LLMs, the cost for trying this out is practically much lower.

karmakurtisaani

6 hours ago

Yep, learning new things is amazing now. Just today I went through some really crappy slides, just dropped them to gemini and asked for elaboration. It saved me hours of figuring the shit out the old fashioned way.

eddythompson80

6 hours ago

I was thinking about that too recently. We have a new service that we’re trying to publish an SDK for. I don’t like the SDK that was created and kept nitpicking about how verbose certain things are and how “unergonomic” it feels (long tedious type names, annoying redundant constructs, etc) But then I was wondering if for a brand new service/SDK if anyone cares anymore and how much fuss i should be making about that.

skhameneh

5 hours ago

This is a bit nuanced, because if there’s redundant constructs then that does impact maintainability and efficiency with both runtime and LLMs working with the code.

I’d push this more towards personal preference of how code is expressed matters much less now than how maintainable it is.

There is the aspect of long type names, they often don’t have much impact when tokenized. The character count of words is nearly negligible - they often become one or two tokens anyways. But, the choice of words may have a greater impact on how the word choice weights an LLMs contextual processing of that word (a human may be able to ignore an inaccuracy in naming a bit more flexibly than some LLMs).

karmakurtisaani

6 hours ago

Exactly, hard to see why any of this matters anymore.

echelon

7 hours ago

I used Rust extensively pre-LLM, and I'm much happier to serialize my thoughts to Rust than any other language.

I prefer to prototype in Golang since it compiles fast and makes for quick iteration, but at the end I ask the LLM to port the Golang to Rust.

Nice Rust enums for APIs are the chef's kiss.

I absolutely will not write anything in Python or scripting languages anymore. They're too brittle and don't have great devex or deployment stories. Especially when you can just as easily build in a typesafe language with good error handling that compiles down to a single static binary.

boredatoms

6 hours ago

It still boggles my mind that golang hasnt introduced rust-style enums

za3faran

6 hours ago

After reading many comments from its original authors, it's not surprising.

karmakurtisaani

7 hours ago

Yep, my point tho is more sentimental than technical. Let the LLM figure out the language details and just manage the output and deployment. It's ... not fun

wannabe44

6 hours ago

> Sure, there is still use for language expertise, but not enough to get excited over new concepts and ideas.

In 5-10 years, the people who have paid attention to these will be needed to bail us out of the mess that the rest of the slop-addled monke brains have created.

karmakurtisaani

4 hours ago

I'm skeptical, most human written code was and still is garbage and survives without major rewrites.

wannabe44

4 hours ago

We will imprison these slop addicted monke brains and start new academic regime, only hand written OCaml code allowed.

Jokes aside, have you watched 2001 - a space Odyssey?

bigstrat2003

4 hours ago

It isn't like LLMs are actually good at programming, so there's no reason to give up on your interest in it. The hype around LLMs is not sustainable, the quality simply is not there.

layer8

6 hours ago

> I remember fondly the pre-LLM days when I used to love reading about programming languages.

First I thought you were going to comment about the grating LLM-isms in the article, which made me end up not enjoying reading it.

gslepak

4 hours ago

I'm honestly baffled why people are writing Zig or Rust and avoiding V. V (and now Bend) seems like the future to me.

altairprime

3 hours ago

Such bafflement is often a medium-strength signal of the Bystander Effect. If someone wrote and submitted to HN your own blog post comparing the Zig/Rust examples from this article to ones from V, I’d read it in its entirety knowing nothing whatsoever about V. Perhaps it will be written by you!

myko

4 hours ago

I haven't heard of those, but I've used Zig and Rust for some projects

What's the pitch?

diek

7 hours ago

> One caveat worth stating up front

> Here’s the actual difference, side by side

I realize the author put a disclaimer at the bottom that they used AI for styling, but having to wade through this stuff at work all day my brain now actively rejects Claude-isms in prose.

myko

4 hours ago

> One caveat worth stating up front

It would be nice if people would note that their posts are AI generated, and put that in the title here to make it easier to ignore

Hugsbox

4 hours ago

I'm not exactly sure what you mean, that seems like a pretty phrase to me, is it considered an LLMism now? Kinda feels like the same thing with the em-dashes; I can't use em anymore because people will then assume I'm an LLM, even though just a few years ago it was a perfectly normal thing to do.

It's entirely possible I'm just getting worse and worse at picking out LLM writing these days too, who knows.

comex

3 hours ago

It’s a very common tic of recent Claude models specifically. Don’t worry - for better or worse, the labs are trying to train away AI writing smells (for example, Anthropic talked about Fable 5.1 having more natural writing), so this particular tic will probably become outdated as an AI indicator relatively soon, as em dashes already have. And then people will forget about it.

bbg2401

7 hours ago

I don’t see the point of letting an LLM generate an article when the topic is your personal, subjective experience which only you, a human, would be able to express.

metaltyphoon

7 hours ago

So anything that has em dashes is now considered LLM generated? What made you think this is generated?

brilee

7 hours ago

"It's young, and it shows"

"Holds up"

"not a toy, but not a sprawling project either, and ideally.."

"And that’s the trap"

ctrl F "real" -> 6 usages

ctrl F "genuine" -> 4 usages

ifh-hn

7 hours ago

Are these obvious signs of AI generation?

tumdum_

7 hours ago

Each one makes me update my estimate slightly in favour of AI. Also, pangram agrees.

layer8

6 hours ago

In aggregate, yes.

And even if LLMs didn’t exist (or had different idiosyncrasies), the article would still be exhibiting a repeatedly weird and stilted writing style.

junon

6 hours ago

A number of "<assertion>: <followup>" patterns too which is pretty common of at least Claude.

Svip

7 hours ago

It kind of says so at the bottom:

> Disclaimer: styling and error handling throughout this article were cleaned up with the help of AI.

The implication seems to be "light editing", but the LLM styling really comes through, so I guess that tracks.

abound

7 hours ago

Not OP, but I think the leading and trailing paragraphs were mostly human-written (and nice to read), but the memory leak example cases had a very different flavor of prose and code comments that smelled very Claude-y to me

metaltyphoon

7 hours ago

After going over that section again I can see it now.

sampullman

7 hours ago

It's unfortunate, but to come across as genuine now I think you have to actively avoid AI-isms.

In this case it feels AI generated with human polish, or vice versa. A couple tells are "One caveat worth stating up front..." and of course, "...the shape of the language itself...".

12_throw_away

2 hours ago

> It's unfortunate, but to come across as genuine now I think you have to actively avoid AI-isms.

I actually think this might be a good thing? I'm way more aware of cliches and filler in my writing these days, and it almost always reads better when I just remove them and plainly say the thing.

applfanboysbgon

7 hours ago

> So anything that has em dashes is now considered LLM generated?

Is that what they said? If that's not what they said, why are you putting words in their mouth in an attempt to weaken their statement into some completely ridiculous stupid strawman that is obviously not actually what they said?

metaltyphoon

7 hours ago

That’s the first thing others point to being LLM-generated. If you see, right after the phrase you quote, there is a question as to what else OP thinks is LLM-generated. I didn’t put words into anyones mouth

12_throw_away

2 hours ago

> [em-dashes are] the first thing others point to being LLM-generated

this isn't true and hasn't been true for a while

> I didn’t put words into anyones mouth

literally read your first sentence again, and notice the word "others"

NetMageSCW

2 hours ago

Substitute for em dashes the inaccurate AI signal du jour. Then consider not posting about whether something is AI or not.

AlienRobot

4 hours ago

In the example of Rust:

    items.iter().enumerate().filter(|(_, i)| cond(i)).map(|(idx, i)| Pointer::idx(i, path, idx)).collect()
vs. Zig:

    while (i < cursors.len) {
        if (actual_index < arr.items.len) { cursors[i] = .{...}; i += 1; }
        else iteration.remove(i);
    }
I think you have to be a special kind of person to call Rust "more readable."

The thing about Rust is that if you can appreciate zero-cost abstractions on iterators then the language feels like the only right way to program. But many programmers either don't use this sort of programming at all, or don't care if it has a cost in languages like JS, Python, Java, etc.

Personally I like Zig a lot because it feels like you're doing low-level programming but without having to program C which is... well, https://xkcd.com/918/

hn_submit

6 hours ago

There will never be a true successor to C since C is "high level assembly." The lack of pointer checking, unsafe casts and non-existent bounds checking aren't an oversight but by design! Assembly doesn't have them so neither does C! There's no reason to whine about it.

The real problem is that people are using C for the wrong reasons. C is for the development of operating systems and low-level code, not applications.

Zig is more modern but anything that does even one iota more of hand-holding or has anything that looks like a guardrail fails the test.

If you want to write applications use Pascal, Java, C#, Swift or Go.

davemp

2 hours ago

Have you written anything low level in Zig? If anything, Zig is lower level than C.

A lot of the “high level assembly” parts of C are actually compiler extensions and not from the C spec.

libc is even worse.

slopinthebag

5 hours ago

i dunno, people are using rust effectively to build applications too. and apple has started using swift in the kernal.

i do agree that applications are probably best built in fast garbage collected language. but it also seems like go, java, C#, etc have other downsides that push people towards things like zig or rust even for apps. it also depends on what you mean by "apps". is a server an app? what about an actual native cross-platform desktop application? does go, c#, or java have a good paradigm for that? what if you want to use an oss language not tied to a big tech company? you start running out of suitable languages pretty dang fast.

you could name any application type and i could probably give you reasons why you might want to build it in a "systems" language instead of a high level one.

monocasa

6 hours ago

Except Rust is actively being used in kernel space and low level embedded systems as well.

hn_submit

5 hours ago

It's useful, but systems programming languages shouldn't be used to create applications in the first place.

We're trying to solve a problem that shouldn't be solved. We're continuing and even confirming the usage of systems programming languages for application development.

everforward

5 hours ago

I don’t think this delineation is that clear, unless by “application” you mean the app tier of a 3 tier app.

Postgres and Nginx make sense in system programming languages; they’re extremely performance sensitive and that granular level of control offers them features. Interpreters are sort of the same, they interact with the OS a ton, it makes sense to work in the same language as the OS.

I do generally agree for the app tier of a web app. I wouldn’t build a CMS in Rust, but I also wouldn’t build a reverse proxy in Python.

hn_submit

5 hours ago

Writing an operating system kernel in C is valid usage since there's a great deal of thought going into it and it almost never changes.

EVERYTHING ELSE is invalid usage no matter how performance critical people claim their application is. These are just excuses for people to use a grossly unsafe language to get that last 2% of performance whilst costing the world trillions in lost productivity and security breaches.

boomlinde

5 hours ago

What are the requirements and design constraints of an application? Please give a general answer that applies to all applications.

monocasa

3 hours ago

Rust is also a great language for app code. I reach for it now in places I used to use python.

tcfhgj

4 hours ago

why shouldn't they? humanity consumes more resources than is sustainable, and systems languages can help to reduce resource consumption.

PaulDavisThe1st

5 hours ago

Is a digital audio workstation or a "AAA" game, with their timing constraints, unbounded computational loads and general demanding performance requirements closer to "an application" or "operating systems and low level code" ?