After writing a lot of Rust, I recently did a small project with Zig to learn the language.
I'm especially impressed with the C interop. You can just import C headers, and Zig will use clang to analyze the header and make all symbols available as regular Zig functions etc. No need for manually writing bindings, which is always an awkward and error-prone chore, or use external tools like bindgen, which still takes quite a bit of effort. Things just work. Zig can also just compile C code.
Rust indeed can feel very heavy, bloated and complicated. The language has a lot of complex features and a steep learning curve.
On the other hand, Rust has an extremely powerful type system that allows building very clean abstractions that enforce correctness. I've never worked with a language that makes it so easy to write correct, maintainable and performant code. With Rust I can jump into almost any code base and contribute, with a high confidence that the compiler will catch most of the obvious issues.
The defining feature of Rust is also the borrow checker and thread safety (Send/Sync), which contribute a lot to the mentioned correctness. Zigs doesn't help you much here. The language is not much of an improvement over C/C++ in this regard. The long-term plan for Zig seems to be static analysis, but if the many attempts for C/C++ in this domain show anything is that this is not possible without severe restrictions and gaps.
Choosing to forego generics and do everything with a comptime abstraction makes Zig a lot easier to understand, compared to Rust generics and traits. The downside is that documentation and predictability suffers. Comptime abstractions can fail to compile with unexpected inputs and require quite a bit of effort. They are also problematic for composability, and require manual documentation, instead of getting nicely autogenerated information about traits and bounds.
Many design decisions in Rust are not inherently tied to the borrow checker. Rust could be a considerably simpler, more concise language. But I also think Rust has gotten many aspects right.
It will be very interesting to see how Zig evolves, but for me, the borrow checker, thread safety and ability to tightly scope `unsafe` would make me chose Rust over Zig for almost all projects.
The complexity of Rust is a pill you have to swallow to get those guarantees, unless you use something like Ada/Spark or verifiable subsets of C - which are both more powerful than Rust in this regard, but also a lot more effort.
Some smaller paper cuts, which are partially just due to the relative youth of Zig:
* no (official) package manager yet, though this is aparently being worked on
* documentation is often incomplete and lacking
* error handling with inferred error sets and `try` is very nice! But for now errors can't hold any data, they are just identifiers, which is often insufficient for good error reporting or handling.
* No closures! (big gotcha)
> * no (official) package manager yet, though this is aparently being worked on
It's the next item on the roadmap as soon as the self-hosted compiler is good enough.
> * documentation is often incomplete and lacking
The language reference is good, but for the stdlib it's best to just read the source code. For other miscellaneous learning materials:
https://github.com/ratfactor/ziglings
https://www.youtube.com/c/ZigSHOWTIME
> * error handling with inferred error sets and `try` is very nice! But for now errors can't hold any data, they are just identifiers, which is often insufficient for good error reporting or handling.
It's still under debate and I'm personally in the camp that errors should not have a payload, so I would avoid assuming that it's definitely the preferable choice. We already have a couple of existing patterns for when diagnostics are needed. That said proposals about adding support for error payloads are still open, so who knows.
https://github.com/ziglang/zig/issues/2647
https://github.com/ziglang/zig/issues/7812
Existing pattern: https://github.com/ziglang/zig/issues/2647#issuecomment-5898...
> * No closures! (big gotcha)
It's possible to create closures already (by declaring a struct type with a method (the closure) and instantiating it immediately after), but it's a clunky solution. We also have an open proposal in this space: