I can't figure out why i am supposed to care about zig beyond it's fun? I get that it's interesting and more safe than C (honestly though what the hell isn 't).
Say you write rust pretty regularly for new product development, what does zig offer to make my life better, my products more stable, etc?
- The small footprint in Zig enables fast compilation (and getting faster) and fast feedback cycles.
- Arbitrary nastiness can happen using From and other traits in Rust, and the tendency to shadow names and rely on type inference can make that unpleasant to track down. In Zig you'd be forced to make a choice (for any non-trivial type) at the return or call site of how you were going to convert things.
- If you want to use the stdlib and have any non-trivial control over how objects are allocated you're in for a rough time in Rust (say, a circular buffer backing some objects and a reusable arena for others). You'll probably be reinventing a lot of wheels.
- Similarly with issues like implicit locking in stdout. It's executing a syscall per line anyway, but the lock can inadvertently make contended writes 1000x slower, so suddenly logging in multithreaded code needs a dedicated logger, and not any of the common options since those fall back to the locking stdout we're trying to avoid.
While you can technically use Rust in these domains you'll find yourself jumping through hoops and fighting against quirks that come with it being fairly high-level and very opinionated on how to enforce memory safety.
One such scenario I've encountered is implementing my own memcpy with a loop like `for i in 0..len`. This works in release builds, but without optimisations this gets a deeeeep callstack that eventually also calls memcpy, so you get a stack overflow. Note how memcpy is implemented in rlibc to avoid this issue: https://docs.rs/rlibc/latest/src/rlibc/lib.rs.html#30-38
It's funny how in rust you can write raw asm, but there seem to be quirks somewhere in the middle.
==========
Why Zig When There is Already C++, D, and Rust?
- No hidden control flow. If Zig code doesn’t look like it’s jumping away to call a function, then it isn’t.
- No hidden allocations. Zig has a hands-off approach when it comes to heap allocation. There is no new keyword or any other language feature that uses a heap allocator. The entire concept of the heap is managed by library and application code, not by the language.
- First-class support for no standard library. Zig has an entirely optional standard library. Each std lib API only gets compiled into your program if you use it. Zig has equal support for either linking against libc or not linking against it. Zig is friendly to bare-metal and high-performance development.
- A Portable Language for Libraries. Zig is attempting to become the new portable language for libraries by simultaneously making it straightforward to conform to the C ABI for external functions, and introducing safety and language design that prevents common bugs within the implementations.
- A Package Manager and Build System for Existing Projects. Not only can you write Zig code instead of C or C++ code, but you can use Zig as a replacement for autotools, cmake, make, scons, ninja, etc. And on top of this, it (will) provide a package manager for native dependencies. This build system is intended to be appropriate even if the entirety of a project’s codebase is in C or C++.
- Simplicity. Zig has no macros and no metaprogramming, yet is still powerful enough to express complex programs in a clear, non-repetitive way. Even Rust has macros with special cases like format!, which is implemented in the compiler itself. Meanwhile in Zig, the equivalent function is implemented in the standard library with no special case code in the compiler.
- Tooling. Zig provides binary archives for Linux, Windows, macOS and FreeBSD. It is installed by downloading and extracting a single archive, no system configuration needed. It is statically compiled, uses LLVM, has out of the box cross-compilation to most major platforms, and ships w/ libc source and dynamically compiles when needed. The Zig build system has caching and compiles C and C++ code with libc support
==========
This one is an odd point; this very article is a demonstration of metaprogramming*:
> Thanks to Zig’s type reflection we can read a row of data into a user-provided type without needing to write any “mapping” function: we know the type we want to read (here the User struct) and can analyse it at compile-time.
* Which is a feature I favor, for the record.
In all honesty, I prefer the syntax of D over all the others, it feels the most like Java or C# but with a lot of modern benefits. D is trying to do too much though it feels like and I would love for the next D standard library to support OOTB similar to what Go supports, especially a very minimalist web server, I think all modern programming languages should be capable of spinning up web servers out of the box. This is one small detail Go got right in my opinion.
Here's an article from the Chromium team on challenges they faced with trying to integrate Rust (or at least evaluating it) note the entry was last updated in 2020:
https://www.chromium.org/Home/chromium-security/memory-safet...
- I use zig as my build system for both rust, zig, C libraries and linking since the build system works really well for this purpose
- When I need to write applications or libraries that can benefit from compile-time code, I always try and use zigs since it's much easier to use comptime then a combination of rust macros and generics
- I like the zig async story a lot better. Or at least it's much easier to wrap my head around and write code in compared to rust + tokyo
On the other hand, sometimes I know a project will benefit from the borrow checker or I want to use some of the awesome rust crates that the community made and I'll use rust instead.
Agree, Tokio is a little tricky for people new to rust no doubt, but it's tricky for a reason. It's saving lives in production.
(A bit off topic rant, but Zig documentation is quite bad, it took me a lot more effort that it should to discover the facts above.)
[1] https://ziglang.org/documentation/master/#Anonymous-List-Lit...
[2] https://ziglang.org/documentation/master/#Anonymous-Struct-L...
I used "ziglearn" to understand ("chapter 1" and "chapter 2" have tons of foundational stuff): https://ziglearn.org/chapter-1/
I used the language reference for the details: https://ziglang.org/documentation/master/#Introduction
I agree the standard library docs aren't useful (unless something changed a lot in the last six months)... it seemed always better to just search and read the standard library source directly.
They're automatically generated. As I understand it they've been holding off on improving them until they have the self hosted compiler working, as it will be easier at that point.
The dot seems quite superfluous and quite the silly quirk (in an unintuitive way). How do Python and Go get by without such a mystery sigil?
SQLite already compiles queries into opcodes and then uses its own VM to interpret them. You would have to reimplement the internal VM.
* arbitrary file I/O. I can take my compile time data and write a python script to put it in a std::array, but I shouldn’t have to.
* non-fixed-sized containers, ie vector
* a generic memoization utility in the standard library for caching. If I want my function to handle any input at runtime, but I want to pre-populate a cache at compile time for values that I know will be called, it’s doable but not as easy as it should be. (In general, given the amount of algorithms that rely on memoization, I’m somewhat surprised that Python is the only language I know that makes memoization as easy as a decorator).
https://ziglang.org/documentation/0.9.1/#embedFile
I haven't tried, but because it should just be equivalent to a string literal you should be able to further process it using comptime as well.
> * non-fixed-sized containers, ie vector
One way is to just have a function count how many entries you need and use arrays. This is all pretty straightforward because comptime.
Real dynamic comptime containers are dependent on making a comptime allocator available. There's a ticket for that: https://github.com/ziglang/zig/issues/1291
> * a generic memoization utility in the standard library for caching. If I want my function to handle any input at runtime, but I want to pre-populate a cache at compile time for values that I know will be called, it’s doable but not as easy as it should be. (In general, given the amount of algorithms that rely on memoization, I’m somewhat surprised that Python is the only language I know that makes memoization as easy as a decorator).
I don't think it's possible to use comptime to generate wrapper functions duplicating the signature of a given function, at least I haven't found a way to do so. The function signature has to be spelled out in the comptime code.
I can give you the Rust perspective, but I'm not sure it's the best.
> arbitrary file I/O
include_str! and include_bytes! make the contents of a file available as a string or a byte array. More complex types would need a build script (or transmute).
> non-fixed-sized containers, ie vector
No support in `const fn`, but you can again use a build script as an escape hatch.
> a generic memoization utility in the standard library for caching
There's nothing in the stdlib, but there are a few third party crates that give you the easy one-line syntax, such as https://crates.io/crates/memoize
There's a proposal for that https://open-std.org/JTC1/SC22/WG21/docs/papers/2020/p1040r6...
> non-fixed-sized containers, ie vector
You can already use them in a constexpr context, you just can't leave it yet. So creating a vector on compile time and then using it on runtime is sadly not working right now but I think that's being worked on as well.
on the same note, lets say if someone is creating something[i am thinking of] like this, what would be a better option, go the forth way or the lisp way cuz both offer same stuff to some extent
How's the Language Server support for this? Last time I tried ZLS, it was quite well rounded so I am curious to know if this type annotations would work with it or not. Would be really cool if they do.
I have never been able to fully adopt zig due to how frustrating working with strings is but I absolutely loved the comptime functionality.
It would be cool to have an official Zig language server that can do it
SELECT * FROM user WHERE age = $age{u16}
You _must_ provide a u16 bind parameter. However the value itself is of course not required to be comptime-known, that would make the whole thing unusable.For what it's worth there are in zig-sqlite variants of the method which bypass the comptime checks; they're not documented properly yet but see all methods named `xyzDynamic`, for example https://github.com/vrischmann/zig-sqlite/blob/master/sqlite....
Can you call comptime-intended code at runtime? No? (Yes? B/c the call site is "in" the runtime code?) But just make it runtime code instead of comptime code?
Great work of the author explaining comptime!