Hey, I made a zig subspace for discussing the zig programming language and projects developed in zig.

Two notable projects developed in zig one could mention are Bun and Ghostty.

Bun homepage
Ghostty homepage
Posted in: s/Zig
🍀 meidam [mod]

Mar 20 · 9 months ago · 👍 clseibold

17 Comments ↓

🐝 undefined · 2025-03-20 at 12:27:

Looking at their website, it seems that the compiler is still as dog-slow as it was the last time I checked. Back to sleep... On a serious note though, my impression of the language is that while the general idea is great, execution leaves a LOT to be desired. The whole stay in beta for 10 years thing is like a programmer equivalent of peter pan syndrome. If it's public and people are using it on real projects, it's not really beta, you're just refusing to take responsibility. And some of the decisions made by the developers (the unused variable thing comes to mind, but it's not the only case) seem pretty arbitrary and annoying.

And just overall the project strikes me as a big idea language (no hidden control flow, compile time execution for everything), and those pretty much universally suffer from usability problems. That being said, I haven't spent any significant time with it, so maybe I'd like it if I did.

Sorry for being negative, I just like being as straightforward as possible.

🐐 drh3xx · 2025-03-20 at 12:33:

@undefined there was something (on the blog?) that mentioned big compilation speedups that at the least will make it quicker than GCC in some cases. Don't think it has hit a release yet though.

My biggest issue is little to no OpenBSD support at the moment. If that was there I'd definitely give it a go.

🚀 stack · 2025-03-20 at 13:31:

Could someone who uses Zig write up an ELI5 as to why I would use it? Not trolling, actually curious why put up with long compiles and even more syntax.

🐝 undefined · 2025-03-20 at 14:10:

@stack its creator, andrew kelly, has a lot of talks on youtube so if you want to hear it from the horse's mouth you could watch those. Basically the idea was, if you like c in general, zig is supposed to be a version of c with the lessons of the last 40 years learned. So manual memory management, no exceptions and so on. It's got arbitrary compile time code execution as well.

🚀 lanterm · 2025-03-20 at 16:35:

Zig has many features that one could wax poetic on, but one aspect of Zig I really enjoy is its approach to error handling. Collectively, you have errors, which are a value belonging to a type called an error enum, which in practice is one half of an error union.

Here's a (maybe contrived, incomplete) sample:

// error enum

const ShoppingError = error { OutOfStock, InsufficientFunds};

// returns an error union, return value will either be a ShoppingError or a u64

fn shop(store: *Store, card: *Card) ShoppingError!u64 {

if (!store.hasStock(.plant)) return ShoppingError.OutOfStock;

if (card.balance < 20) return ShoppingError.InsufficientFunds;

store.deductStock(.plant, 1);

card.balance -= 20;

return 20;

}

// The try keyword will "unwrap" the error union.

// If there is an error, it will bubble up.

// Otherwise, the return value from shop will be assigned to amount_spent.

const amount_spent = try shop(store, card);

There are other keywords related to error handling in addition to try. Overall, I find Zig's approach to error handling to be really elegant and I appreciate the focus at a language design level to such an important aspect of programming.

🚀 stack · 2025-03-20 at 19:22:

Thank you -- I am less interested in the author's opinions as opinions of people who actually use it.

🚀 stack · 2025-03-20 at 20:47:

@lanterm: does Zig somehow hide a type field to resolve whether the returned value is an error or a u64? The implication is that there is at least an extra bit passed back in addition to the 64 required for a u64. I don't know if I would want that baked into the language instead of a good exception unwinding mechanism (or nothing at all), but I suppose it would work...

Is the a!b syntax capable of arbitrarily unionizing any type? More than two? Or is it just for errors?

🍀 meidam [OP/mod] · 2025-03-21 at 09:49:

@stack Pretty sure the "a!b" syntax is only for errors, where the "a" is the error type and "b" is the intended return type. You can even do a "!b" for a catch all if you know that the function can return errors but it can be multible kinds of errors.

🚀 clseibold [🛂] · 2025-03-22 at 05:05:

@meidam Yes, "a!b" and "!b" are called error union types - which is just fancy language for a type that "unionizes" (combines) with an error type; a!b is a type of type b or error type a. "!b" is a type of either type b or a general error type. The values for these can be either an error value of type a *or* a value of type b; and for !b, it would be either a value of *any* error type, or a value of type b. Hopefully this makes sense.

It's a little bit of functional programming type-system -esque stuff peeking into Zig, lol. Same with optional types (?Rect) too. I think these are both pretty good additions, personally.

🚀 stack · 2025-03-22 at 14:37:

I don't think that is a feature worth the syntax, personally. A language that allows multiple return values provides the same capability without the extra syntax (and multiple values are pretty useful in other situations).

Handling errors after every function call is kind of nuts, and again, there are proven (for many decades) ways to catch and unwind errors.

I don't want to pooptalk any language here -- I was hoping to hear something that would inspire me instead of just forcing me to repeat the old 'why is everyone so interested in reinventing the wheel' saw.

But if you enjoy coding in whatever language - by all means do so!

🚀 lanterm · 2025-03-27 at 21:06:

@stack There definitely is a storage cost to error unions. It's a valid concern. Consider this simple Zig program and its output:

const std = @import("std");

const StoreError = error{

InsufficientFunds,

OutOfStock,

};

pub fn main() !void {

std.log.info("{d}", .{@sizeOf(u64)});

std.log.info("{d}", .{@sizeOf(StoreError)});

std.log.info("{d}", .{@sizeOf(StoreError!u64)});

std.log.info("{d}", .{@sizeOf(u8)});

std.log.info("{d}", .{@sizeOf(StoreError)});

std.log.info("{d}", .{@sizeOf(StoreError!u8)});

}

info: 8

info: 2

info: 16

info: 1

info: 2

info: 4

The @sizeOf builtin returns the number of bytes it takes to store a type in memory.

— https://ziglang.org/documentation/0.14.0/#sizeOf

You can see that, due to alignment, error unions may take up more space than their constituent parts. I haven't dove in and investigated the exact memory layouts of this myself, however.

As for creating unions of arbitrary types, rather than an error union, Zig has union types for that. Here's an example from Zig's docs.

— https://ziglang.org/documentation/0.14.0/#union

const Payload = union {

int: i64,

float: f64,

boolean: bool,

};

test "simple union" {

var payload = Payload{ .int = 1234 };

payload.float = 12.34;

}

🚀 lanterm · 2025-03-27 at 21:09:

@clseibold I agree about error unions and optionals being good additions to the language. The code I write ends up more expressive and maps to my intent more. When I'm programming in another language without these constructs and related keywords, trying to handle all the cases just feels clunky.

🚀 lanterm · 2025-03-27 at 21:30:

@stack

I don't think that is a feature worth the syntax, personally. A language that allows multiple return values provides the same capability without the extra syntax (and multiple values are pretty useful in other situations).

Zig also lets you define tuple types and even destructure the result into variable declarations, if that's more your cup of tea, but of course you wouldn't typically use that feature for t error handling because of Zig's other features. The fact that errors are a first class citizen in Zig makes it so the compiler can help you more when it comes to writing robust software in an elegant way.

Handling errors after every function call is kind of nuts, and again, there are proven (for many decades) ways to catch and unwind errors.

In Zig, you aren't required to declare the return type of your function as an error union - you'll typically only do that if the function can return errors. The benefit of this is that if a function does not return an error union, you have a reasonable level of confidence that errors won't occur there. When you're reading, debugging, refactoring code, this is really nice.

I also want to note that Zig has try/catch syntax for dealing with errors, but it's nothing like exception handling in other languages which use the same keywords. try "unwraps" either an error or a value from an error union and catch allows you to execute another statement in the event of an error. Consider the following few snippets:

// assign value to num or return error

const num = try parseNum(str);

// equivalent to the line above

const num = parseNum(str) catch |err| return err;

// if number parsing fails, assign default value of 0

const num = parseNum(str) catch 0;

// return a different error than the one returned from parseNum

const num = parseNum(str) catch return error.InvalidInput;

// you might want to provide a default value if str is empty or error if it's straight up not a number

const num = parseNum(str) catch |err| switch (err) {

error.Empty => 0,

error.NaN => return error.InvalidInput,

}

// note that the above would probably be better structured like

const num = if (str.len > 0)

parseNum(str) catch return error.InvalidInput

else

0;

Side note, I sincerely apologize for all of the poor code indentation in these code samples. I imagine this doesn't do a great job at showing how nice Zig looks to read, but I'm writing this via Lagrange on mobile, alas I'm stuck with it stripping the leading whitespace from my input.

🚀 stack · 2025-03-27 at 21:47:

Alignment is a curious issue -- modern x86 CPUs do not care much about alignment other than across cache lines. Sometimes outdated ideas keep springing up in new implementations.

🚀 clseibold [🛂] · Jul 01 at 09:32:

Honestly, find it hilarious that the C programmer might be arguing for try and catch with exceptions, lol.

Anyways, I fundamentally disagree with languages that try to shoehorn every error into an exception that must be handled with try-catch crap. Not every error should crash your program just because it's "unhandled", lmao.

Golang's method is better, Zig and Odin's methods are *even* better than Golang.

It's literally the job of the programmer to take care of errors, and if you can't then just explicitly pass it up to the caller. The implicit exception crap you get in Java is just bad.

Anyways, if you traded the amount of time you spend bashing on things you don't know about and instead study them, then you might find that neither language forces you to write a bunch of boilerplate code to handle errors after every function call. Now it's absolutely true for Golang, but not for Odin nor Zig.

🚀 stack · Jul 01 at 21:40:

If you are referring to me, WTF, man! Don't you have anything better to do, like rewrite all your stuff again to fix all those memory-safe bugs? Sheesh.

Also, I am really a Smug Lisp Weenie, not a C programmer anyway. I use whatever is around that makes sense, not just the newest/shiniest stuff.

🚀 stack · Jul 04 at 15:46:

@lanterm, thank you for the clarifications.