Zig is emerging as a compelling alternative for systems programming. It offers C interoperability, manual memory management with safety features, and compile-time execution that enables powerful metaprogramming—all without the complexity of C++ or Rust’s borrow checker.
Why Zig?
Zig targets the same niche as C: operating systems, embedded systems, and performance-critical applications. But it addresses C’s footguns while remaining simple:
- No hidden control flow: No operator overloading, no hidden allocators
- Compile-time execution:
comptime enables powerful metaprogramming - C interop: Drop-in replacement for C, uses C libraries directly
- Optional safety: Debug builds include bounds checking, use-after-free detection
Hello, Zig
1
2
3
4
5
| const std = @import("std");
pub fn main() void {
std.debug.print("Hello, {s}!\n", .{"World"});
}
|
Build and run:
1
2
| zig build-exe hello.zig
./hello
|
Manual Memory Management Done Right
Zig requires explicit allocators, making memory behavior transparent:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| const std = @import("std");
pub fn main() !void {
// Use the general purpose allocator
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
// Allocate an array
var list = std.ArrayList(i32).init(allocator);
defer list.deinit();
try list.append(1);
try list.append(2);
try list.append(3);
for (list.items) |item| {
std.debug.print("{d} ", .{item});
}
std.debug.print("\n", .{});
}
|
The defer keyword ensures cleanup happens—similar to Go’s defer or RAII in C++.
Compile-Time Execution with comptime
Zig’s comptime enables code execution at compile time:
1
2
3
4
5
6
7
8
9
10
11
12
| const std = @import("std");
fn fibonacci(n: u64) u64 {
if (n <= 1) return n;
return fibonacci(n - 1) + fibonacci(n - 2);
}
pub fn main() void {
// Computed at compile time!
const fib_10 = comptime fibonacci(10);
std.debug.print("Fibonacci(10) = {d}\n", .{fib_10});
}
|
This enables:
- Zero-cost abstractions: Generic code with no runtime overhead
- Compile-time validation: Check invariants before the program runs
- Code generation: Generate specialized code based on types
Generic Programming
Generics in Zig use comptime types:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
| fn LinkedList(comptime T: type) type {
return struct {
const Self = @This();
pub const Node = struct {
data: T,
next: ?*Node,
};
head: ?*Node,
allocator: std.mem.Allocator,
pub fn init(allocator: std.mem.Allocator) Self {
return .{
.head = null,
.allocator = allocator,
};
}
pub fn prepend(self: *Self, data: T) !void {
const new_node = try self.allocator.create(Node);
new_node.* = .{
.data = data,
.next = self.head,
};
self.head = new_node;
}
pub fn deinit(self: *Self) void {
var current = self.head;
while (current) |node| {
const next = node.next;
self.allocator.destroy(node);
current = next;
}
}
};
}
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
var list = LinkedList(i32).init(gpa.allocator());
defer list.deinit();
try list.prepend(3);
try list.prepend(2);
try list.prepend(1);
}
|
Error Handling
Zig has explicit error handling without exceptions:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
| const std = @import("std");
const FileError = error{
NotFound,
PermissionDenied,
Unknown,
};
fn readConfig(path: []const u8) FileError![]const u8 {
// Simulate file reading
if (std.mem.eql(u8, path, "missing.txt")) {
return FileError.NotFound;
}
return "config data";
}
pub fn main() void {
// Handle with catch
const config = readConfig("config.txt") catch |err| {
std.debug.print("Error: {}\n", .{err});
return;
};
std.debug.print("Config: {s}\n", .{config});
}
// Or propagate with try
fn loadApp() !void {
const config = try readConfig("config.txt");
_ = config;
}
|
C Interoperability
Zig can directly use C headers:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| const c = @cImport({
@cInclude("stdio.h");
@cInclude("stdlib.h");
});
pub fn main() void {
_ = c.printf("Hello from C!\n");
const ptr = c.malloc(100) orelse {
c.printf("Allocation failed\n");
return;
};
defer c.free(ptr);
// Use the memory...
}
|
Build with C libraries:
1
| zig build-exe main.zig -lc
|
Building a Simple HTTP Server
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
| const std = @import("std");
const net = std.net;
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const address = try net.Address.parseIp("127.0.0.1", 8080);
var server = try address.listen(.{
.reuse_address = true,
});
defer server.deinit();
std.debug.print("Listening on http://127.0.0.1:8080\n", .{});
while (true) {
var conn = try server.accept();
defer conn.stream.close();
var buf: [1024]u8 = undefined;
const n = try conn.stream.read(&buf);
if (n > 0) {
const response =
"HTTP/1.1 200 OK\r\n" ++
"Content-Type: text/plain\r\n" ++
"Content-Length: 13\r\n" ++
"\r\n" ++
"Hello, Zig!\n";
_ = try conn.stream.write(response);
}
}
}
|
Cross-Compilation
Zig excels at cross-compilation:
1
2
3
4
5
6
7
8
| # Compile for Windows from Linux
zig build-exe main.zig -target x86_64-windows
# Compile for ARM Linux (Raspberry Pi)
zig build-exe main.zig -target arm-linux-gnueabihf
# Compile for macOS
zig build-exe main.zig -target x86_64-macos
|
Zig as a C/C++ Build System
Zig can compile C/C++ code with its built-in toolchain:
1
2
3
4
5
| # Compile C code
zig cc -o hello hello.c
# With optimizations and cross-compilation
zig cc -O3 -target aarch64-linux -o hello hello.c
|
Conclusion
Zig offers a pragmatic approach to systems programming:
- Simple, readable syntax without hidden complexity
- Powerful compile-time execution for zero-cost abstractions
- Seamless C interoperability
- Excellent cross-compilation support
At Sajima Solutions, we’re exploring Zig for performance-critical components where C’s simplicity is desired but its safety footguns are not. Contact us to discuss your systems programming needs.