Hacker Newsnew | past | comments | ask | show | jobs | submitlogin
Zig got a new ELF linker and it's fast (github.com/ziglang)
123 points by Retro_Dev 8 days ago | hide | past | favorite | 50 comments




The more I hear about Zig, the more I appreciate it. Its vertically integrated stack (with the custom linker and code-generation backends) stands out to me as a really compelling feature that enables interesting optimizations. The compiler is also much easier to interact with in a consistent way compared to C. I've been using it as an experimental backend for my language project with great results.

I've been writing Zig since the beginning of the year. Just playing around with some small stuff really. I want to love the language. It does so much right, but there's something about it just feels off to me. And then the recent Io change really bugged me.

I suspect a lot of this is due to the language being new and unpolished. Zig is still actively being shaped, and therefore fundamental pieces of the Zig standard library still change frequently.

I really like Zig, but I do think you have to accept some rough edges and breaking changes when using it. Give it another decade and I am hopeful it will be much more stable.


I agree, and I try to ignore that stuff, but there are a few things that I think are a little more fundamental then that.

This one is fairly minor, but the overly granular namespacing drives me nuts. I shouldn't have to go four or five layers deep to get fairly common functionality. The pattern of returning structs from within structs from within structs just gets old. Maybe that has to do with the way the language handles generics, I don't know.

The attitude to interfaces is also grating. I get that Kelly and the Zig team want to discourage there use, but with the changes to Io it feels a bit like they want to have their cake and eat it to. Before it was mainly just allocators, but now readers, writers, and it sounds like async are all going to be handled through interfaces. But yet the community writ large is still expected to not really use them. Seems like they should have sorted their attitude and approach to interfaces before making these most recent changes.


Reader and writers were interfaces before 0.15.1. What’s new is that they are buffered. I don’t think that zig discourages interfaces, just that the language doesn’t hide anything. An interface in all system languages is a struct with a vtable just like in zig but that they have an easy way of creating one. If you need one in zig you can go that approach or use a tagged union just that zig is open about what an interface is.

> f you need one in zig you can go that approach or use a tagged union just that zig is open about what an interface is.

Perl objects worked like this and the ecosystem is a mess as a result. There's a built in `bless` to add a vtable. There's some cool stuff you can do with that like blessing arrays to do array of structs vs struct of arrays and making it look like a regular object in user code. The problem is there are like 4 popular object libraries that provide base stuff like inheritance, meta objects, getters/setters etc and they're not all compatible and they have subtle tradeoffs that get magnified when you have to pull multiple in to get serious work done.


Yes I know. I see the same statement every single time this conversation comes.

Let me ask this: hypothetically if they wanted to, would it be possible for Zig to add a language feature called Interface which provided to the user "...an easy way of creating one".


Creating an interface is fairly easy once you know how. I would say that it is as easy as in rust for a beginner. You get used to it and I don’t believe it needs a special keyword.

That's not what I asked.

Zig will be forced to introduce interfaces or traits or whatever they want to call it just like Go was forced dragged, kicking and screaming to introduce generics.

Nobody is going to want to live without proper interfaces or an equivalent in 202x instead of DIY hacks. Its a fundamental, building-block of software development. You will have a gazillion complaining in Zig forums after its 1.0 release.


The analogy with Go is quite interesting, because in the end, after Go got its generics, the actual usage of this feature is quite minimal compared to what I expected :)

That's an interesting assertion but does not hold to reality. Nearly every single modern Go library leverages Generics in some manner AFAIK. It is a feature primarily meant for library authors anyways. Go generics are still limited - nowhere as powerful as Rust/C++ or even Java's generics. Primarily because they added it too late and ran into considerable trouble, instead of upfront thinking about the design.

Interfaces however are far more fundamental. Any app with components and modularization would declare intent through interfaces. Not having interfaces/traits as a language feature is like living without a hand or leg. Sure you can limp and hop, but it will get rather tiresome.


I have the same feeling, and my best guess is that it's the intentional (and imo arbitrary) friction that has been sprinkled into the language. And camelCase.

Lol. Funny enough I actually like camel case because I've spent so much time in Java.

Yeah, I can see that. It's also difficult to put my finger on, but for a language that claims to be simple it seems to make a lot of things needlessly complicated. I'm also not loving how everything is deeply nested structs so I have to do struct.doThefirstThing.doTheSecondThing.doTheThirdThing().etc() all the time.


I agree with the "off" part. I've thought maybe it's the syntax? It's unclear, but I'm just hoping the feeling will go away! I find the IO changes to be strange, it's almost as if Zig is encoding an effect system a-la Haskell (i.e. this function will have an allocation/IO side effect).

> I find the IO changes to be strange

Perhaps you just feel that way because of 10+ years of accreted expectations?

To me, it feels like a reinvention of an interesting idea: Fastbufs

https://www.ucw.cz/libucw/doc/ucw/fastbuf.html

I will concede that the ergonomics of Io in Zig right now are pretty rough sledding.


I said this elsewhere, but I think the awkwardness is coming from Zigs attitude towards interfaces. It seems like from a language design perspective they've been made intentionally awkward because they want to discourage their use, and fair enough. There are other options for sure. But now they want to base Io around interfaces. So Writers and Readers, it sounds like Async as well, and of course Allocators were already using interfaces. If they are going to become that fundamental to core features of the language, I think a more ergonomic way to work them needs to be a higher priority.

> It seems like from a language design perspective they've been made intentionally awkward

Awkward relative to what?

Relative to C? Did you look at my link to Fastbufs? Take a look at that and then get back to me how awkward Zig is relative to that.

Zig seems to be aiming to be a better C. Full stop. If you want abstraction, RAII and other higher-level stuff, C++ and Rust exist.

> If they are going to become that fundamental to core features of the language, I think a more ergonomic way to work them needs to be a higher priority.

I don't disagree. However, there have really only been two times so far that significant "interfaces" have churned like this (Allocgate and Writergate). Allocgate was driven by the fact that the previous implementation had real, measurable performance issues. Presumably, Writergate is being driven by similar problems. I'm actually really happy that someone is designing a language while paying very strict attention to the performance of both the compiler and the compiled code.

It's really hard to generalize until you get a couple of concrete examples under your belt. Trying to make an "interface" more ergonomic when it may get nuked from orbit in a couple of versions is kind of pointless.

I have plenty of gripes about Zig, but "interface churn" or "ergonomics" aren't very high on my list. I signed up for a language that is pre-1.0, so, while I may get annoyed, I also have to admit that I did this to myself.


> Zig seems to be aiming to be a better C.

Modula-2 and Object Pascal already did that for me, before I cared with C on MS-DOS, unfortunely one did not come with UNIX as selling point, and the other had the two industry giants that cared doing the wrong decisions.


In general I agree with you, Zig is obviously trying to give as much control as possible and avoid higher level abstractions, but it's not like they haven't added elements to the language to improve ergonomics and make the right choice the easy choice.

For loops with a capture group. Under the hood Zig is doing bounds checking and binding the result of each iteration to the variable in the capture groups.You can add a range to that and Zig will iterate it for you. One could argue this is a "higher level" abstractation compared to a traditional for loop and accessing an array by index, but it exists because it naturally pushes developers to write more robust code.

The try keyword, being short hand for catch err return err. A Go developer might argue that it's better to be explicit and not have try. But it's a common pattern and it's far more ergonomic to use the try keyword then it is to catch and return every single time.

Shit, you could argue that the entire idea of comp time is an abstraction to allow code generation. Someone like Ginger Bill would argue you're better off writing a separate program to generate source code then use meta programming.

With the upcoming IO interface and the plans for async, it seems to me that interfaces are going to play a much more prominent role in the language. It makes sense they might want to consider abstractions that push developers towards good design when working with them.


Io as an interface isn’t released yet, I say wait and let’s try it out. Maybe it’s crap and something else will be implemented like the other previous async solutions in zig. Since zig isn’t a 1.0 lang yet it’s expected to have some pitfalls and breakage.

> I've been using it as an experimental backend for my language project with great results.

One annoyance that I've ran into when using Zig as a transpiler backend is the lack of unstructured goto. Many languages don't need that, but if you're dealing with the one that does, converting such code is non-trivial.


Yeah, this is one of the bigger pain points (that and no dynamic stack allocation). I got around this by defining anonymous functions and then calling between them (using `@call(.always-tail, ...)` where possible to avoid stack-frame overhead).

Eventually, I restructured my IR to allow for more imperative code-generation, which I believe will lead to slightly better optimizations by the compiler.


Labeled switches come pretty close. Is there a particular case you have in mind where they don't suffice?

Ah, I wasn't aware of those - they seem to be a fairly recent addition? It definitely does simplify things for many common patterns, but not all, unfortunately; consider the case of a goto into the middle of a loop in C, or even something like Duff's Device.

Yeah, it's fairly new. Maybe 0.14.1?

Those cases are definitely supported. Here's a reasonably faithful re-interpretation of Duff's device which generates the right assembly. If you'll take my word for it, code and assembly equivalent to goto in the middle of a loop body isn't an issue either. The only thing you're losing is the unreadable syntactic interleaving you can do in C.

    pub fn copy(T: type, noalias dst: []T, noalias src: []const T, unroll: comptime_int) void {
        if (unroll < 2)
            @compileError("No-op unrolls not supported");
        std.debug.assert(dst.len == src.len);
        std.debug.assert(dst.len > 0);

        var n: usize = (src.len + unroll - 1) / unroll;
        var i: usize = 0;
        duff: switch (src.len % unroll) {
            inline 0,2...unroll => |x| {
                dst[i] = src[i];
                i += 1;
                continue :duff (unroll + x - 1) % unroll;
            },
            inline 1 => {
                dst[i] = src[i];
                i += 1;
                n -= 1;
                if (n == 0)
                    break :duff;
                continue :duff 0;
            },
            else => unreachable,
        }
    }

To remind, the original context was transpiling another language (with semantics that allows irreducible CFGs) to Zig, not just this one specific pattern. Yes, of course, for pretty much all practical code, there is going to be a better equivalent. But the transpiler doesn't get the benefit of only working with practical code - it has to deal with whatever the source language spec says is legal. So you need to come up with a generic solution to the entire class of problems presented. Which does exist - e.g. Relooper (https://mozakai.blogspot.com/2012/05/reloop-all-blocks.html) - but the point is that it is very much a non-trivial transformation, to the point where it might actually be easier to transpile to C or something else that has full-fledged goto that you can just use directly.

Relooper goes the other direction though. We don't need to produce high-level control flow. For a transpilation-to-Zig use case the algorithm can be extremely dumb. Naively translate every loop (or any other control flow containing nontrivial internal gotos) into a labeled switch, then partition the bodies at each goto statement, making a new label for each such location.

The only thing you're really losing is that the goto is bounded by an enclosing function, but that's also the case in C, C++ and other languages written by non-masochists. From a developer ergonomics perspective you _might_ want a proper goto instead of having to rely on a clunky general-purpose transformation as described above (though I've yet to see that use case), but from a transpilation perspective it's extremely easy to just treat labeled switch as a bunch of gotos, replace other control flow with gotos, and lower an entire function into something that compiles optimally.


I don't really have much interest in Zig the language, but Zig as a standalone C/C++ compiler is pretty great.

I'm using it as a cross-compiler for linux-arm64 because its much simpler to download a single archive and extract it somewhere than to waste a bunch of time on guessing how each different Linux distro does ARM64 cross compilers (or doesn't in the case of Fedora).


Same for me.

The more Zig-the-language is hyped, the more I see it doesn't bring anything of interest. Zig-the-toolchain, on the other hand, is neat.


I’ve been using it for an embedded project to target arm thumb and the typical x86-64 hosts that communicate with a protobuf based protocol. It’s absurdly convenient to be able to just give windows users the repo, zig binary, and tell them to run ‘zig build’.

This is exactly how I've been using it the last couple years and it's incredibly nice.

Between mold and this, the linker space appears to be going through a renaissance.

Does anyone know if it’s reasonably easy to use elf2 as a standalone linker in a c/c++ toolchain? Or is it specially built just for Zig?


Looking in the source tree, it doesn't look like it has it's own entry point. Zig is heading in the direction of a very verticall integrated compilation stack (I believe this is where most of the speed up comes from), so I'm not really sure of the utility with using it outside of the Zig world.

https://github.com/jacobly0/zig/tree/4508c2543508e04253471e1... https://github.com/jacobly0/zig/blob/4508c2543508e04253471e1...


> this is where most of the speed up comes from

I might be mistaken, but the brief look at code shows that the speed up appears to come from combination of async architecture (the selling point of Mold) and intelligent usage of PUNCH_HOLE/INSERT_RANGE fallocate() operations.

Surprisingly enough PUNCH_HOLE and friends have already matured to be production ready, with viable support from ext4 and xfs filesystem. The possibilities!


I stand corrected! It would be interesting to try and use the Zig linker for something else, would be a neat weekend project.

> Between mold and this, the linker space appears to be going through a renaissance.

No kidding. There are also https://github.com/davidlattimore/wild and https://github.com/kubkon/bold.


Just going to mention the book Linkers and Loaders by John R. Levine, I'm not sure if there's anything comparable to it.

Crafting Interpreters?

While also a great book, there is very little overlap in content to Linkers and Loaders.

If I recall correctly, this is one of the final pieces that allows zig to be used as a fully self-contained cross-compiling C toolchain (once its linker is enabled for more platforms / formats)

I might be misunderstanding, but I don’t think that's quite accurate. As I understand it, Zig ships a Clang frontend and wraps it with precompiled sysroots. Unless they're developing an LLVM backend, I'm not sure how it could serve as a completely self-contained toolchain.

zig has a cross-compiler frontend (zig cc) which can be used as a drop-in replacement for a custom sysroot + binutils + gcc for several platforms. I've used it to build OCaml cross-compilers that only depend on zig itself (https://www.chrisarmstrong.dev/posts/ocaml-cross-compilation...).

There are other projects that have used it in a similar way too.

[1] https://actually.fyi/posts/zig-makes-rust-cross-compilation-... [2] https://jcbhmr.com/2024/07/19/zig-cc-cmake/


Interesting. OCaml cross compilation scared me the one time I considered it, so it's nice to know this is an option. I will be pedantic and say it isn't a replacement for a custom sysroot/binutils/whatnot, since it really just papers over those user-facing details by shipping a prebuilt collection of sysroots and using LLD. They sorta get this for free since they need it for cross-compiling Zig code anyways.

Zig does have its own native x86_64 backend for debug compilation.

ref: https://ziglang.org/devlog/2025/#2025-06-08


They're also getting an aarch64 backend soon (or has it already landed)? Really looking forward to this one for development on my Mac!

My point was that Zig uses their backends (which includes their homegrown ones), but since `zig cc` is a wrapper around Clang, it always goes through the `LLVM` path and sidesteps the Zig backend(s).


Ah. I see what you are saying now. I believe you are correct.

And it fixes a bug about debug output. Seems like a bigger deal than fast.

Seems like we spoke too soon. Latest results shows that it's not as great as it seems

Zig honestly blows my mind. I think it's clearly the best next gen language because of the build system alone.



Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: