Hacker News new | ask | show | jobs
Failing to Learn Zig via Advent of Code (2022) (forrestthewoods.com)
14 points by takemine 894 days ago
4 comments

Article created in January 2022 reflecting on AoC 2021. Zig changed a lot since then.
Was written in 2022. I wonder if now, in 2024, the state of Zig is different.
A big chunk of complains is addressed in 0.11.
It's not that long ago, but maybe adding (2022) is warranted given the rapid pace of development of Zig, the standard library and the documentation and its browser.

> Building is slow. It takes about ~3 seconds minimum which is frustratingly slow when I'm fighting basic syntax errors.

I don't remember how 0.8.1 was, but syntax analysis now happens early and syntax errors will interrupt the compilation process relatively immediately. You still run into a pretty slow compile time if you for example misspell a function you use from an import.

> Zig reference documentation badly needs examples. Can't figure out how to use std.fmt.parseInt.

This is a fair point. If I find the description lacking, I tend to look at the tests. The current version of the Zig documentation will link to the source file of the implementation, where you'll find those.

> Compiler should be able to trivially detect not calling nums.deinit() at compile-time.

Without a concept of ownership and borrowing, I don't think so but for the most trivial uses.

> Need to access myArray.items[idx] instead of myArray[idx]. I get it. But very unintuitive and requires knowledge of implementation details.

It was rather well documented up front then as it is now that you access the elements of an ArrayList via its item field. To be blunt, it's not that Zig's solution requires knowledge of implementation details, but that OPs mismatched assumption requires a preconceived notion of how it should work.

> Should I pass the allocator to every function? Doesn't seem great. Maybe I'm supposed to create a global? Globals are evil and feel bad.

Outside library code intended to be consumed by others, this is largely a matter of taste. If you think globals are evil and feel bad, don't use that, but I personally see no reason to generalize the effects they have on organization and readability.

> How do I get the length of a tokenized line? What does tokenize return? It returns anytype.

Very valid criticism, but this has improved lately. The current std.mem.tokenizeAny returns a TokenIterator. It doesn't have advance knowledge of the number of tokens because they are consumed in place.

> How do I make a lambda / closure / local function? There's a too complicated pattern with numerous issues. There is, of course, a GitHub issue discussing this.

I just want to mention that the example here is a bit misleading. A function in a struct doesn't need to be a method, so there's no need to pass a self only to dismiss it. But it is still fundamentally true that there's no concept of closures and that your only means of creating a local function is by defining a local type that contains it.

> Bit-shifting is a monumental pain in the ass. const mask : usize = @as(usize, 1) << @truncate(u6, i);. I eventually wound up with: @truncate(u16, @as(usize, 1) << @truncate(u6, i));. Blech.

Agreed, this is blech. There are abstractions over plain bit shifts in std.math.shl and std.math.shr that improve this situation somewhat.

> Can't redirect output of zig build run. The following doesn't work: zig build run > c:/temp/out.txt. :(

std.debug.print will output to stderr, and the author is redirecting stdout.

> Can't xor bools!?

a != b for booleans a and b is xor. That there is a distinct operator for this rather than the one you'd use for integers can however be a pain in the ass when the type of a and b is parameterized and could be either booleans or integers. Then you have to switch on the type and provide a special case for booleans.

I strongly agree with some of the concluding sentiments. Documentation currently is rather poor. I don't mind a lack of tutorials so much as the standard library code itself has provided enough examples for me to get by. I don't think it's ready for prime time, mostly because of the documentation and the fact that Zig is still a moving target. Not so much because of the specific criticisms the author raised.

> No one in the history of the world has ever been confused or upset by a + b calling a function.

I find this statement very, very dubious! I think operator overloading does have its merits, but I can understand the point of view that the potential for confusion outweighs the benefits.

For simple vector types, Zig addresses the problem in the example through a built-in concept of vectors that support arithmetic operations, though still not directly supporting operations mixing scalars and vectors; you have to explicitly @splat scalars. The author's example should by now be something like

    const Vec3f = @Vector(3, f32); 

    fn lerp(a: Vec3f, b: Vec3f, t: f32) Vec3f {
        const tv: Vec3f = @splat(t);
        const ones: Vec3f = @splat(1);
        return a * (ones - tv) + b * tv;
    }
which still leaves some room for improvement. See for example https://github.com/ziglang/zig/issues/17274
January 17th, 2022