Notes on the Zig programming language
Sun Feb 04 2024E.W.Ayers
I had the chance to work on some Zig code over the weekends:
I forked zigup
I had a go at using Mach, but gave up.
I used raylib to render maps from this dataset
I wrote some variants of A* plus visualisations on these maps.
Overall it's been a pleasant experience. It's much easier to work with than working in C and C++. I know that I should really just learn Rust instead but I'm just not excited by it.
1. Links about Zig
I'll keep updating this section.
2. Things that are annoying but are deliberate choices
No operator overloading. This is insufferable for geometry and linear algebra. You can use the builtin operators with
@Vector
, but no broadcasting except with clunky@splat()
builtin. Vectors are a pain to use with casting.Casting between different numeric types is verbose
Most of my error handling is to handle
OutOfMemory
errors. If there was special language support for allocators then 90% of mytry
s would go away and the code would be much less cluttered.Having to deal with integer cast issues gets old. The fact that Zig is littered
@intCast()
calls is because integer casts in C are traumatic. To understand why Zig was designed this way we have to look at how integer casts work in C: it's completely mental. I found this article really useful for understanding why it is done this way.Having to pass allocators around everywhere gets old. I prefer the Jai approach where there is a
context
keyword containing an allocator.The type hinting doesn't really work for some generics like Reader.
3. Things that are annoying because they are not mature
Hidden pass-by-reference unsoundness is unforgivable. Values are values and pointers are pointers. The optimiser can't convert values to a reference if the value is being mutated.
The LSP isn't good enough yet.
Something has gone wrong in my VSCode setup which means that it doesn't show diagnostics on save.
There needs to be an official zig version manager. It's kind of cute just downloading zig as a file but it becomes old setting PATH variables all the time. You also need the correct
zls
to be on the path, so now you need to manage the version of two things and it's too much fuss. see alsoReader
vsAnyReader
vsGenericReader
is confusing and not clearly documented.no coroutines. Having to explicitly write state for iterators is annoying. This is on a roadmap. I am excited to try out the Zig approach.
the type inference is not great, and I found myself having to tediously write down types where it shouldn't really need them.
Maybe I missed it but the capture in a for loop is always usize?
The debugger works, but it's a C++ debugger so it's rough around the edges.
4. Things that are amazing
Not having to worry about linkers and header files and cmake and ninja and cross-platform compiles is amazing and I love it. It just works.
defer
is so good.comptime
is so good. There is a blog post in me about what type theorists can learn from comptime.I like how down-to-earth it is compared to Rust. Rust wants you to be an architecture astronaut.
5. If I was designing zig
I would include the ZLS project as a core part of Zig.
Version manager
zigup
is built in to zig.Have a
context
system like Jai. Maybe this can be implemented in userland. A template is contextvars.I would revert to a more conventional exception handling system, (
try
,catch
,finally
like in C++ or JavaScript), but keep the!
and error sets. The exceptions that can be thrown (ie the error set) are inferred statically (eg how Koka does it). You can hover over a method and see the exceptions that might be thrown. You get compile time errors if there is an unhandled exception kind. You can tag a method as 'pure' meaning it never throws and this is enforced. I think the Zig error design falls into the same 'coloured function' trap thatasync/await
falls into in other languages (which is elegantly avoided in Zig). I'm ok to drop this because I know this was a very explicit design choice.Be way more chill about implicitly casting between the different numeric types. Delete
@intToFloat
,@floatToInt
,@intCast
. 90% of the time it gets in the way. I am ok with runtime detectable-undefined-behaviour. At the very least it should be less cluttered, eg just writeu16(x)
to cast to au16
. Or maybex as u16
. It's particularly annoying because I couldn't figure out how to type thei
infor (0..10) |i| {}
. It seems to beusize
no matter what.Coroutines. We have
resume/suspend
so it's a small step to supporting general coroutines. I think this is on roadmap.Operator overloading should be supported. I would love to be able to define my own operators for types. I guess this could quickly spiral out of control into a rust-style trait system. I wonder if traits could be implemented with comptime.
I want to contribute more to the docs of Zig. There is something about Rust that meant it never caught my excitement, I don't really get it because it has everything I want. Meanwhile I am really excited about Zig despite all the teething problems and issues. I don't get it. Maybe I am over functional programming.