Page created: 2023-05-22

, updated: 2023-05-31

drawing of a sad rat sitting on the curb while three lizards say…​

“I learned Zig in a weekend! …​Six hours! …​6µs!” say the blissful lizards.
Followed by something to the effect of, “It’s easy to pick up because
the syntax is so simple.”

Clearly, many folks really do find Zig easy to learn. Veteran C programmers,
in particular, seem to find Zig a natural and logical next step.
Kelley’s fun and enlightening talk,
The Road to Zig 1.0
(youtube.com),
presents Zig as exactly this: “C, but with the problems fixed.”

And it’s also true that Zig has a relatively small amount of syntax.
For some, it is apparently possible to “pick it up” in the span of time it takes to read
a single page of documentation
(ziglang.org).
(Mind you, that “single page” is on the order of 256 printed pages.)

The problem, of course, is that there’s not a direct correlation between the
slimness of a language’s syntax and ease of learning. If there were, the
simplicity of Lisp’s S-expressions would make it trivial to “pick up” during
lunch. Or Forth, with it’s space-separated words would take, well, you just
learned it.
Enjoy! Of course, I’m being a bit silly here. Zig is a
relatively “small” and “simple” language and that does aid learning. (By
contrast, nobody but a savant is going to “pick up” C++ or Rust from scratch in
an afternoon. They’re just too big for that.)

But I’d like to state for the record that if you find Zig difficult to master,
you are not alone. If your programming background is like mine (or even if it’s less weird than mine),
Zig can be challenging.

I’d like to enumerate the reasons I believe this is, and why most of the
reasons are actually very wonderful things about the language and well worth
the effort to overcome.

Zig is new

Let’s get this out of the way: You can’t just go into the bookstore and buy a
Zig book. And even if you could, it would be out of date in a month. Zig is
changing rapidly. (If I didn’t have help, I doubt I would have been able to
keep
Ziglings
(github.com)
up to date on my own when I was busy with other projects!)

Crucially, there is basically no documentation for the standard library
except for the source code itself. (To be fair, some parts are well commented
and much of it is surprisingly readable.) Everything else is scattered across
the Web, but you’ll have to see for yourself if the examples still compile.

Why that’s good: It’s not. It may be exciting, but it’s
terrible for learning. Let’s move on.

Zig forces you to make choices

Depending on your background, you might also not be used to having to think in
terms of exact numeric types (u8, i16, f64, etc.) for every single
runtime value in your program
. Handling “strings” means dealing with pointers,
sentinel termination, arrays, and slices. This will slow you down very quickly
if you’re used to dynamic languages which (very conveniently) handle these
details for you.

If you’re new to manual memory management, you already have a pretty big hurdle
to get over.

That’s true regardless of language.

But Zig throws another level of decision-making at you that (most?) developers are
rarely asked to make: Choosing a memory allocation strategy.

And you won’t get far without making a decision. A significant portion of the
Zig standard library requires that you provide an allocator.

That’s a heck of a thing to ask of a beginner!

Why that’s good: This is actually one of the coolest things about Zig.
To write performant software (or even just as a learning exercise),
we should learn about obtaining and using memory. We should be
allowed to pick the best types and allocators for our application.

(It’s also perfectly fine to pick something like i64 for numbers and the
GeneralPurposeAllocator to get you going. You can always change strategies
later.)

Zig is pedantic

Something that makes Zig harder to learn up front, but easier in the long
run is lack of undefined behavior.
While C presents itself as a compact language, learning how to avoid
undefined behavior takes time to master because the language itself
is more than happy to let you do all sorts of incorrect things.
Zig makes undefined behavior an error.

I’ve been a Rust noob and a Zig noob. The thing they have in common is fighting
with the compiler because I know what I want to do, but I don’t
know how to express it.

Zig’s type system is logical enough, but it still takes time to learn how to
create (and especially) cast types correctly because there are so many possible
combinations of the basic building-blocks:

  • var vs const

  • optional values (?)

  • error unions (!)

  • single vs many-item pointers ( and [])

  • slices of arrays ([] and [x..y])

  • sentinel termination ([n:null])

Put enough of these together and you can end up with something like
[*:null]const ?[*:0]const u8
(a real example I personally struggled with early on).

There are certain bits of code I could have written faster in assembly language
than Zig because it took me a long time to figure out how to express my intent.

Why that’s good: Zig is trying to help. C and assemblers don’t much
care what I do with my memory, so they make it “easy” to write code that
performs actions regardless of type. But if I get it wrong (and I will), the program
will segfault. The language won’t get in my way, but it won’t help me either.
Zig makes you get it right, and that’s a good thing. Slower and more tedious,
especially at first, but good.

Zig has comptime

Plenty of languages have metaprogramming, but none are exactly alike.
C’s preprocessor won’t prepare you for Zig’s compile time execution.
Macros won’t prepare you for it.
The run-time introspection of various dynamic languages won’t prepare you
for it either.

Zig’s comptime is its own thing. And you’ll need to learn about it
or you’ll run into errors early on that otherwise won’t make sense
because it’s happening whether you ask for it or not.

Within a specific set of rules (including the explicit use of the comptime
keyword), “regular” Zig code will run at compile time, resulting in a runtime
executable with values pre-calculated, unneeded code removed, loops “unrolled”,
and code generated inline to work with different data types.

Why that’s good: Only time will tell if comptime is the
“greatest thing since sliced bread” or not. But so far, it seems to be
a pretty solid concept. By executing portions of the program at compile
time, a large number of tricky language problems have been solved without
introducing too many additional concepts.

What is “hard” versus “easy”, anyway?

The
Zig Programming Language
(ziglang.org)
home page doesn’t contain the word “easy”, but it does contain
the word “simple”.

As Rich Hickey teaches us in his brilliant
Simple Made Easy
(youtube.com),
easy things seem easy primarily because they are familiar.
Easy is subjective.
But simple things are simple because they do not complicate; they have fewer
concepts
.
Simple is objective.

(Simple is often elegant as well, but then we’re back to being subjective.)

Zig may be easy for some and not for others (like me!), but it definitely
strives to be simple, uniform, and correct. And for that reason, I think it’s
worth the investment.

Read More