Zig is presented as a simple language with a
Focus on debugging your application rather than debugging your programming language knowledge.
It started out as an implied C replacement, but has since grown to become very opinionated, in my opinion. :-) It does retain more of the simplicity of C, and its designer, Andrew Kelley, is really focused on maintaining that simplicity. But while Kelley himself is quite an amazing guy from the streams I've seen of him, it feels like Zig is being overly restrictive by trying to force the "One True Way" philosophy on its users, which ends up just causing lots of headache debugging the language.
Some upsides
---
- compile time execution!
- extremely simple and readable generics
- completely interops with C without much friction. It can compile C, translate C to Zig, and import .h files.
- maybe a C backend? I think it will eventually output C, but that code isn't finished yet unless I'm mistaken.
- `defer` statement (runs on scope exit) and `errdefer` (only cleans up if there was an error before leaving scope)
- really nice community, quick to offer help (I can't stress this enough, they are really great people)
Some downsides
---
- There is no operator overloading, so vector math suffers in a big way, although its builtin SIMD features might alleviate this somewhat.
- It doesn't allow function overloading, either. This isn't a deal breaker, but C'mon, it was one of the two decent features from C++ actually worth keeping.
- It also has no default function arguments.
There may be work-arounds for those last two, but again, more language debugging.
One of the worst offenders, however, is the lack of `for` loops. It has a builtin foreach, but no ranges. So you can only loop over known-length arrays (either compile-time known or runtime). So we have to resort to `while` loops for iterating over an integer range. Here is a workaround for the missing `for` loop:
1 2 3 4 5 | {var i: u32 = 0; while (i < rects.len) : (i += 1) { // do the things... }} |
Notice how the entire structure has to be wrapped in a block so the index doesn't leak into the scope. We are living in 2021, right? But having a `for` loop would mean 2 ways to loop over a range, so it is taboo from what I can tell.
Here is a (half?) joke response to an actual serious proposal to deal with the lack of a basic `for` loop.
1 2 3 4 5 6 | // Joke proposal if (with(@as(usize, 0))) |*i| while (i.* <= 10) : (i.* += 1) { std.debug.print("{}\n", .{ i.* }); } // The serious proposal: with(var x: usize = 0) while(x < 10) : (i += 1) {...} |
And the equivalent shorter brainf!ck code:
-[>+<-----]>---<++++++++++<++++++++++[>>.+<.<-]>>---------.-.
On Simplicity
---
On the overview page, we are lead to believe that a port of C code to Zig is significantly simpler due to error handling. And perhaps it is somewhat simpler due to Zig having `defer` statements, which are nice. But I went to check out the original logic code after the error handling is done, and this is representative of what it's like to deal with pointers:
1 2 3 4 | // C code: *ptr = sample; // Zig code: @ptrCast(*f32, @alignCast(@alignOf(f32), sample_ptr)).* = sample; |
And the main reason given for lots of rejected proposals is that Zig must be readable, with only one way to do things.
Last is an example of me trying to port over Casey's famous dead-ballz-simple-and-fast memory arena code that we all fell in love with upon first watching him code it without any language friction whatsoever.
It took me 9 hours of language debugging, issue tracking, web and/or soul searching to come up with this beauty. I'm only posting the core logic here because I've already lost the brevity contest in this post.
1 2 3 4 5 6 7 8 9 10 11 | if (arena.used + size <= arena.base.len) { // This first attempt works, but what a mess... //var memory = @bitCast([]align(@alignOf(T))u8, arena.base[arena.used..]); //result = @ptrCast(*T, memory[[email protected](T)]); // Another way to do it! (please don't tell anyone) var address = @ptrToInt(arena.base.ptr + arena.used); result = @intToPtr(*T, address); arena.used += size; } |
Looking back on this snippet, I suppose making int casting and pointer casting explicit isn't that big of a deal, but the shear amount willpower required to fight the language was unexpected. And the way I got this to work is probably seriously frowned upon for being unsafe.
All in all, it's probably better than C++ due to its simplicity, but I wish Zig was the silver bullet.