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.