Introduction to Zig, a Potential Heir to C
David Eastman takes a look at Zig, perhaps 'the new C'. It's a low level language that gets developers closer to the metal, in a modern way.
Jan 27th, 2024 4:00am by
Photo by Rob Lambert on Unsplash
Testing Zig
I first install Zig onto my trusty Mac:
> brew install zig
#include <stdio.h>
int main(int argc, char **argv)
{
printf("Hello world\n");
return 0;
}
- My C code meets the pre-processor, which adds the code from header files (those with the .h suffix), strips out comments and generally makes sure that you are left with plain code.
- The compiler then turns the C code into assembler language, or an intermediate equivalent.
- The assembler then creates “object code” or binary. We are now getting nearer.
- Finally, the linker brings in all the library code needed to actually do anything useful on my machine.
(Ignore the time it took to build — my Mac is almost a decade old). Zig claims to be faster than C, but those types of claims are best examined in better technical detail for the area you need.
OK, that’s C. But what about “hello world” for Zig?
const std = @import("std");
pub fn main() void
{
std.debug.print("Hello, world!n", .{});
}
Yes, it just overwrote my C version of hello.
But here is the cool thing. Without extra tooling, I can cross compile to, say, Windows:
That is quite a thing. As Zig describes it, “Cross-compiling is a first-class use case”. (I haven’t tested this file on a Windows machine, so YMMV.)
But let’s take a look at what else Zig is offering.
Testing and Build Safety
Zig understands that debugging low-level code is treacherous, so it has various ways to combat this. Zig comes with a test runner and test block built in. This gives you a chance to isolate areas of concern:
const std = @import("std");
test "expect addOne adds one to 41"
{
try std.testing.expect(addOne(41) == 42);
}
fn addOne(number: i32) i32
{
return number + 1;
}
We get a glimpse into the declaration of variables: the addOne functions expects a 32-bit integer and emits one in return.
Zig defines four build flags:
- The standard Debug. This compiles quickly and enables safety checks, so sacrifices speed and size for testing convenience.
- ReleaseFast. This optimizes for speed, but sacrifices compilation speed and size.
- ReleaseSafe. For live testing, this keeps safety checks on.
- ReleaseSmall. Keeps the binary small.
Do You Remember? Because Zig Doesn’t
The reason you aren’t using C right now is largely to do with its lack of memory management, as I’ve already mentioned. Low-level languages don’t have garbage collection, and leave you to clean up. Or crash. However, Zig does provide defer as an escape pod to fix things if they go awry:
const std = @import("std");
const expect = std.testing.expect;
test "allocation"
{
const allocator = std.heap.page_allocator;
const memory = try allocator.alloc(u8, 100);
defer allocator.free(memory);
try expect(memory.len == 100);
try expect(@TypeOf(memory) == []u8);
}
Race to the Bottom
So Zig is, if you like, trying to win the race to the bottom, to be the new underpinning of computing. The heir to C. It is also trying to be simpler (with fewer keywords) and a safer platform. The hidden comparison I appear to be making is with Rust, but they probably already have different audiences. Like Rust, Zig has a very active contributor community so is already guaranteed a future. But beware: simplicity is quite hard.
YOUTUBE.COM/THENEWSTACK
Tech moves fast, don't miss an episode. Subscribe to our YouTube
channel to stream all our podcasts, interviews, demos, and more.
